java多线程 线程创建、控制、同步、通信
这里写目录标题
多线程
一、进程与线程
1、进程
特点(特性):
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
基本状态:
**2)运行状态(Running):
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
*** 3)**阻塞状态(Blocked):
由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。
2、线程
-
定义:
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 - 同一进程中的多条线程将共享该进程中的全部系统资源
特点:
-
允许跨线程资源共享
状态:
-
新建态
线程与进程的关系:
-
线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源
线程与进程的区别:
地址空间和其它资源:
进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
通信:
进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
调度和切换:
线程上下文切换比进程上下文切换要快得多。
3、举例详细说明
-
工厂与工人的关系:工厂为进程,工人为线程
-
工厂中有多个生产流水线:每条流水线上有许多工人,流水线为进程,工人为线程
-
工业园与工厂:工业园为进程,工厂为线程
二、线程的创建和启动
java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例
1、继承Thread类创建线程类
-
定义Thread类的子类,并重写该类的run()方法,run()方法的方法体就代表了线程需要完成的任务。
-
创建Thread子类的实例,即创建了线程对象(new 子类())
-
调用线程对象的start()方法来创建并启动该线程
public class MyThread extends Thread { @Override public void run() { System.out.println("线程Id=" + this.getId() + "\t线程名=" + this.getName()); } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
2、实现Runnable接口创建线程类
-
定义Runnable接口的实现类,并重写该接口的run()方法,run()方法的方法体就代表了线程需要完成的任务。
-
创建Runnable接口的实现类的实例,并以此作为Thread的target来创建Thread对象,此对象即为线程对象
-
调用线程对象的start()方法来创建并启动该线程
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("线程Id=" + Thread.currentThread().getId()+"\t线程名="+Thread.currentThread().getName()); } public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } }
-
Thread.currentThread():currentThread()是Thread类的静态方法(类方法),该方法会返回当前正在执行的线程对象
-
getId(),getName(),是是Thread类的实例方法(成员方法),该方法会返回当前线程对象的id,name
实现Runnable接口创建线程类是通过实现一个接口,并以此实现类作为target,Thread类的属性(构造器的参数)来创建线程对象,所以可以以匿名内部类的形式创建线程对象,java1.8后更可以使用lambda表达式书写
public class MyRunnable { public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("线程Id=" + Thread.currentThread().getId()+"\t线程名="+Thread.currentThread().getName()); } }); thread1.start(); Thread thread2 = new Thread(()->{ System.out.println("线程Id=" + Thread.currentThread().getId()+"\t线程名="+Thread.currentThread().getName()); }); } }
3、使用Callable和Future创建线程
- 分析一下Callable和Future类结构关系
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); } public class FutureTask<V> implements RunnableFuture<V> { .... } public interface Callable<V> { V call() throws Exception; }
-
创建Callable接口的实现类,并重写call()方法,再创建Callable实现类的实例(对象)
call就是子线程执行的主体 - 类似于run方法,区别run方法 call方法有返回值
- call可以抛出异常
使用FutureTask类来包装Callable对象,创建FutureTask类的对象作为Thread对象的target创建并启动新线程
可通过FutureTask类的对象的get()方法获取子线程执行结束后返回的结果,即call()方法的返回结果
public class MyCallable implements Callable<Long> { @Override public Long call() throws Exception { System.out.println("子线程Id=" + Thread.currentThread().getId() + "\t子线程名=" + Thread.currentThread().getName()); return Thread.currentThread().getId(); } } public class MainTest { public static void main(String[] args) throws ExecutionException, InterruptedException { //写法一 FutureTask<Long> task = new FutureTask<>(new MyCallable()); new Thread(task).start(); long threadId = task.get(); System.out.println("Callable返回值为:" + threadId); } }
public class MainTest { public static void main(String[] args) throws ExecutionException, InterruptedException { //写法二 FutureTask<Long> task1 = new FutureTask<>(() -> { System.out.println("子线程Id=" + Thread.currentThread().getId() + "\t子线程名=" + Thread.currentThread().getName()); return Thread.currentThread().getId(); }); new Thread(task1).start(); long threadId1 = task1.get(); System.out.println("Callable返回值为:" + threadId1); } }
4、创建线程三种方式的比较
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,其中实现Runnable和Callable接口方式类似,可将实现Callable接口的方式视为实现Runnable接口的加强,Callable有返回值可抛出异常。所以创建线程方式的比较主要是看继承Thread类与实现接口类两类方式的优缺点。
-
继承Thread类方式:
优势:访问当前线程对象可用this代替Tread.currentThread(),编写简单。(真没找出别的优势了。。。) - 劣势:java中只能单一继承,不能继承其他父类
实现接口的方式:
-
优势:可继承其他父类;多个线程对象可共享一个target对象(适用于多个相同线程处理同一份资源)
推荐使用实现接口的方式,需要返回值实现Callable,不需要返回值实现Runnable
三、线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x29E10Ip-1596957064223)(C:\Users\freedom\Desktop\笔记\image-20200806155309894.png)]
(我也忘了图是哪找的了。。。。网上找的,如有侵权,联系我删掉)
- 新建态
- 就绪态(可运行态)
- 运行态
- 死亡态
- 阻塞态: IO流(等待用户输入)
- Thread.sleep
- t.join
- wait()
关于更多的线程的生命周期,可以看这个链接中的内容
https://www.cnblogs.com/xidongyu/p/10962657.html
四、控制线程
1、join线程:join();
Thread提供了一个让一个线程等待另一个线程完成的方法join方法。
public final void join() throws InterruptedException等待这个线程死亡。
/** * @author yhp * @CreateDate 2020/8/7 - 10:29 * @Description: join为线程的方法,在哪个线程中调用则阻塞哪个线程, * 在thread0线程中调用thread1的join方法则阻塞thread0线程,等待thread1线程运行死亡 */ public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread threadMain = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "开始执行"); Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { //子线程thread中,调用主线程的join方法,则阻塞当前子线程thread,主线程运行死亡后才执行当前线程 //threadMain.join(); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread正在执行。。。。"); } System.out.println("thread执行结束/"); }); thread.start(); //主线程中调用子线程thread的join方法,则阻塞主线程,子线程thread死亡后主线程才再次运行 // thread.join(); for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "main正在执行。。。。"); } System.out.println("main执行结束/"); } }
2、后台线程:thread.setDaemon(true);
- 后台线程,又称为“守护线程”或”精灵线程“。
- 后台线程在后台运行,他的任务是为其他线程提供服务的,所有非后台线程称为前台线程,前台线程结束后后台线程会跟随的结束,无论后台线程是否运行结束。
public class DaemonThread implements Runnable { @Override public void run() { //后台线程循环100次,每次1秒 for (int i = 0; i < 100; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + i); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new DaemonThread()); thread.start(); //将此线程设置成后台线程 thread.setDaemon(true); //启动后台线程 thread.start(); //主线程继续运行,主线程循环5次,每次1秒 for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "\t" + i); } //主线程到此运行结束进入死亡态,同时后台进程也随之进入死亡态(无论后台线程是否运行完) } }
3、线程睡眠:sleep()
- Thread类的类方法(静态方法),线程睡眠可使线程从运行态进入阻塞态。
public static void sleep(long millis)throws InterruptedException
-
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 线程不会丢失任何显示器的所有权。
-
参数 millis - 以毫秒为单位的睡眠时间长度
public static void sleep(long millis,int nanos) throws InterruptedException
- 导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。 线程不会丢失任何显示器的所有权。
- 参数
millis
- 以毫秒为单位的睡眠时间长度 nanos
-0-999999
额外的纳秒睡眠
4、线程让步:yieId
- Thread类的类方法(静态方法),对调度程序的一个暗示,即当前线程愿意让出cpu资源,重新与其他线程争夺cpu资源分配
public static void yield()
- 对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。
5、线程优先级
-
每个线程执行时都具有一定的优先级,优先级高的线程获得cpu资源的几率更大,优先级低的线程获得cpu资源的几率更小。(注:优先级的高度只影响几率,并非一定高的先执行)
-
每个线程默认的优先级玉创建它的父线程优先级相同。默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。(注:main线程优先级默认为5)
-
Thread类有两个成员方法,setPriority(int newPriority)、getPriority(),分别为设置优先级和获取指定线程的优先级。
-
优先级范围:1~10
Thread.MAX_PRIORITY:10 最高优先级 - Thread.MIN_PRIORITY: 1 最低优先级
- Thread.NORM_PRIORITY:5 普通优先级
五、线程同步
1、同步代码块
多个用户同时对一个账户进行取款操作:
-
账户实体类(一个普通的实体javaBean)
public class Account implements Serializable { private Integer id; private String name; private Double money; public Account() { } public Account(Integer id, String name, Double money) { this.id = id; this.name = name; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
线程类,在run()方法中,使用synchronized关键字,创建代码块
public class AccountThread extends Thread { private Account account; private Double money; private Thread thread; public AccountThread() { } public AccountThread(Account account, Double money) { this.account = account; this.money = money; } public void setThread(Thread thread) { this.thread = thread; } @Override public void run() { synchronized (account) { if (account.getMoney() >= money) { double newMoney = account.getMoney() - money; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(newMoney); System.out.println("账户名=" + account.getName() + "\t取款额=" + money + "\t账户余额=" + account.getMoney()); } else { System.out.println("账户名=" + account.getName() + "余额不足"); } } } }
-
测试类
public static void main(String[] args) throws InterruptedException { Account account = new Account(1008601,"张三",1000d); Thread thread1 = new AccountThread(account,200d); Thread thread2 = new AccountThread(account,800d); thread1.start(); // thread1.join(); thread2.start(); Scanner in = new Scanner(System.in); in.next(); System.out.println("---"); } }
2、同步方法
-
实体类,与同步代码块不同的是,同步方法时用synchronized修饰方法,在实体类中创建同步方法
public class Account implements Serializable { private Integer id; private String name; private Double money; public synchronized void deposit(Double money) { if (this.getMoney() >= money) { double newMoney = this.getMoney() - money; try { //睡眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setMoney(newMoney); System.out.println("账户名=" + this.getName() + "\t取款额=" + money + "\t账户余额=" + this.getMoney()); } else { System.out.println("账户名=" + this.getName() + "余额不足"); } } public Account() { } public Account(Integer id, String name, Double money) { this.id = id; this.name = name; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
线程类
public class AccountThread extends Thread { private Account account; private Double money; public AccountThread(Account account, Double money) { this.account = account; this.money = money; } @Override public void run() { account.deposit(money); } }
-
测试类
public class MainTest { public static void main(String[] args) throws InterruptedException { Account account = new Account(1008601,"张三",1000d); Thread thread1 = new AccountThread(account,200d); Thread thread2 = new AccountThread(account,800d); thread1.start(); // thread1.join(); thread2.start(); Scanner in = new Scanner(System.in); in.next(); System.out.println("---"); } }
3、同步锁
同步锁方法与同步方法的线程类与测试类相同
-
实体类
public class Account implements Serializable { private Integer id; private String name; private Double money; //定义一个可重现锁 private final ReentrantLock lock = new ReentrantLock(); public void deposit(Double money) { //加锁 lock.lock(); if (this.getMoney() >= money) { double newMoney = this.getMoney() - money; try { //睡眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setMoney(newMoney); System.out.println("账户名=" + this.getName() + "\t取款额=" + money + "\t账户余额=" + this.getMoney()); } else { System.out.println("账户名=" + this.getName() + "余额不足"); } //解锁 lock.unlock(); } public Account() { } public Account(Integer id, String name, Double money) { this.id = id; this.name = name; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
4、死锁
实例:
public class Test { public static String objA = "strA"; public static String objB = "strB"; public static void main(String[] args) { Thread thread1 = new Thread(new Lock1()); Thread thread2 = new Thread(new Lock2()); thread1.start(); thread2.start(); } } class Lock1 extends Thread{ @Override public void run(){ try{ System.out.println("Lock1线程正在运行"); while(true){ synchronized(Test.objA){ System.out.println("Lock1线程获得并持有了Test.objA的锁"); Thread.sleep(1000); System.out.println("Lock1等待Test.objB的锁..."); synchronized(Test.objB){ System.out.println("Lock1获得并持有了Test.objB的锁"); } } } }catch(Exception e){ e.printStackTrace(); } } } class Lock2 extends Thread{ @Override public void run() { try{ System.out.println("Lock2线程正在运行"); while(true){ synchronized(Test.objB){ System.out.println("Lock2线程获得并持有了Test.objB的锁"); Thread.sleep(1000); System.out.println("Lock2等待Test.objA的锁..."); synchronized(Test.objA){ System.out.println("Lock2线程获得并持有了Test.objA的锁"); } } } }catch(Exception e){ e.printStackTrace(); } } }
六、线程通信
下面将针对一个账户的取款,存款的操作描述线程间的通信。(同一个账户,多人存款最多同时存在一笔存款,一人取款,有存款方可取)
1、test1_synchronized
-
实体类
/** * @author yhp * @CreateDate 2020/8/9 - 9:00 * @Description: 线程间的通信,使用synchronized修饰的同步方法,账户实体类 */ public class Account { private String accountId; //账户id private double balance; //账户余额 private boolean flag = false; //标识账户中是否已有存款的旗标 public Account() { } public Account(String accountId, double balance) { this.accountId = accountId; this.balance = balance; } public double getBalance() { return balance; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } @Override public String toString() { return "Account{" + "accountId='" + accountId + '\'' + ", balance=" + balance + ", flag=" + flag + '}'; } //取款方法 public synchronized void draw(double money) { try { //判断账户中是否有存款,若无则阻塞,使用while循环,确保本次取钱成功为止 while (!flag) { Thread.yield(); wait(); } if (flag) { Thread.sleep(10); //取钱 System.out.print(Thread.currentThread().getName() + "取钱:" + money); balance -= money; System.out.println("\t账户余额=" + balance + "\n"); //唤醒其他线程,将标识符改为false flag = false; notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } //存款方法 public synchronized void deposit(double money) { try { //判断账户中是否有存款,若有则阻塞,使用while循环,确保本次存钱成功为止 while (flag) { Thread.yield(); wait(); } if (!flag) { //存钱 System.out.print(Thread.currentThread().getName() + "存钱:" + money); balance += money; System.out.println("\t账户余额=" + balance); //唤醒其他线程,将标识符改为true flag = true; notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
线程类,分为取款线程与存款线程
public class DepositThread extends Thread { private Account account; //用户账户 private double money; //取款金额 public DepositThread(String name,Account account, double money) { super(name); this.account = account; this.money = money; } @Override //存钱操作 public void run() { for (int i = 0; i < 10; i++) { account.deposit(money); } } }
public class DrawThread extends Thread{ private Account account; //用户账户 private double money; //取款金额 public DrawThread(String name,Account account, double money) { super(name); this.account = account; this.money = money; } @Override //取钱操作 public void run() { for (int i = 0; i < 30; i++) { account.draw(money); } } }
-
测试类
public class MainTest { public static void main(String[] args) { Account account = new Account("10086",0d); new DrawThread("取钱A",account,10000d).start(); new DepositThread("A他爸",account,10000d).start(); new DepositThread("A他妈",account,10000d).start(); new DepositThread("A他爷",account,10000d).start(); } }
2、test2_Condiition
-
实体类与同步方法相同
/** * @author yhp * @CreateDate 2020/8/9 - 9:00 * @Description: 线程间的通信,使用synchronized修饰的同步代码块,账户实体类 */ public class Account { private String accountId; //账户id private double balance; //账户余额 private boolean flag = false; //标识账户中是否已有存款的旗标 //显示定义Lock对象 private final Lock lock = new ReentrantLock(); //获取指定lock对象对应的Condition private final Condition condition = lock.newCondition(); public Account() { } public Account(String accountId, double balance) { this.accountId = accountId; this.balance = balance; } public double getBalance() { return balance; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } @Override public String toString() { return "Account{" + "accountId='" + accountId + '\'' + ", balance=" + balance + ", flag=" + flag + '}'; } //取款方法 public void draw(double money) { //加锁 lock.lock(); try { //判断账户中是否有存款,若无则阻塞,使用while循环,确保本次取钱成功为止 while (!flag) { Thread.yield(); condition.await(); } if (flag){ //取钱 System.out.print(Thread.currentThread().getName() + "取钱:" + money); balance -= money; System.out.println("\t账户余额=" + balance + "\n"); //唤醒其他线程,将标识符改为false flag = false; condition.signalAll(); } }catch (InterruptedException e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } } //存款方法 public synchronized void deposit(double money) { //加锁 lock.lock(); try { //判断账户中是否有存款,若有则阻塞,使用while循环,确保本次存钱成功为止 while (flag) { Thread.yield(); condition.await(); } if(!flag){ //存钱 System.out.print(Thread.currentThread().getName() + "存钱:" + money); balance += money; System.out.println("\t账户余额=" + balance); //唤醒其他线程,将标识符改为true flag = true; condition.signalAll(); } }catch (InterruptedException e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } } }
3、test3_阻塞队列生产者消费者模式,生产西瓜
-
生产者
//生产者 每100ms生产一个西瓜放入框中,一个筐能装10个瓜,装满后运给消费者消费 public class Producer extends Thread { private Integer watermelon = 2; //西瓜 private Integer[] frame = new Integer[5]; private long time; //生产2个瓜消费的时间 private BlockingQueue sharedQueue; //运送西瓜的队列 public Producer(String name,long time,BlockingQueue sharedQueue) { super(name); this.time = time; this.sharedQueue = sharedQueue; } @Override public void run() { //生产者一直生产西瓜,最多生产5框 while (true){ try { //每次生产2个西瓜,10个一筐 for (int i = 0; i < 5; i++) { Thread.sleep(time); frame[i] = watermelon; if(frame[4] == watermelon){ System.out.println(this.getName() + "升产了10个西瓜并将西瓜运往消费者------"); sharedQueue.put(frame); Thread.sleep(100); frame[4] = 0; } } } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
消费者
public class Consumer extends Thread { private Integer[] frame; //筐 private BlockingQueue sharedQueue; //运送西瓜的队列 private Lock lock = new ReentrantLock(); public Consumer(BlockingQueue sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { //消费者者一直在消费西瓜 while (true) { try { Thread.sleep(100); //取出队列中的筐 frame = (Integer[]) sharedQueue.take(); System.out.println("消费者消费了一筐10个西瓜,队列中剩余" + sharedQueue.size() + "筐西瓜\n"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
测试类
public class MainTest { public static void main(String[] args) { //阻塞队列,一次队列最多运算2筐西瓜 BlockingQueue sharedQueue = new LinkedBlockingDeque(2); Thread consumer = new Consumer(sharedQueue); Thread producer1 = new Producer("张三",100l,sharedQueue); consumer.start(); producer1.start(); } }
- 多线程之线程创建、加锁同步、通信(Java学习笔记六)
- java多线程,线程的创建,生命周期,同步,通信,线程池
- 黑马程序员——JAVA基础——线程---概述,创建、生命周期,控制,同步,线程通信
- java多线程 -- Condition 控制线程通信
- Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程
- Java笔记3 多线程<1>线程概述、多线程的创建、多线程的安全问题、静态同步函数的锁、死锁
- Java —— 多线程笔记 一、线程创建、启动、生命周期、线程控制
- 黑马毕向东Java课程笔记(day11):多线程(第一部分)——进程与线程+线程创建+线程安全与同步代码块+同步锁/死锁
- Java线程同步和线程间通信代码和控制线程关闭
- java基础——多线程(线程的同步互斥与通信)
- java多线程(三) 线程的同步与通信
- java中的多线程——线程创建方式、线程互斥和线程间通信
- java多线程学习-同步之线程通信
- Java知识点整理:第十四章:java多线程编程、创建一个线程、异步与同步、java反射、类加载
- JAVA基础学习(十一)--多线程一线程的创建,运行,同步和锁
- java多线程学习之创建线程与线程间通信
- Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程
- Java精选笔记_多线程(创建、生命周期及状态转换、调度、同步、通信)
- java进程、线程、线程生命周期,创建线程的五种方法、以及多线程并发同步问题。
- java多线程(同步&线程间通信详解&消费者生产者模式&死锁&Thread.join()) 多线程编程(二)