基础——线程
< 返回列表时间: 2020-06-27来源:OSCHINA

一、线程执行的内存原理 public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); }
对应的 内存原理图 大致是这样:

注意事项:
1.执行线程任务的run方法是线程私有的。
2.某个线程对象出现异常不会影响其他线程的执行。
二、创建线程的方式
(1)继承Thread类
1、步骤
1.定义一个类,继承Thread类。
2.重写Thread类的run方法。
3.创建线程对象。
4.调用start方法开启新线程,内部会执行run方法。
2、代码示例 public class MyThread extends Thread { @Override public void run() { // 获取线程名称 String threadName = this.getName(); for (int i=0;i<100;i++) { // 复习异常抛出的方法,抛出一个运行时异常 if("Thread-1".equals(threadName) && i == 3){ throw new RuntimeException(threadName + "出问题了"); } System.out.println(threadName+"..."+i); } } }
3、Thread类的构造函数 public Thread():分配一个新的线程对象。 public Thread(String name):分配一个指定名字的新的线程对象。 public Thread(Runnable target):分配一个带有指定目标新的线程对象。 public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
4、常用成员方法 public String getName():获取当前线程名称。 public String start();导致此线程开始执行;java虚拟机调用此线程的run方法。 public void run():定义线程的任务逻辑。
5、常用静态方法 public static void sleep(long millis): 让当前正在执行的进程暂停指定毫秒数。 public static Thread currentThread():返回对当前正在执行的线程对象的引用。

(2)实现Runnable接口
1、步骤
1.定义Runnable接口的实现类
2.实现类覆盖重写run方法,指定线程任务逻辑
3.创建Runnable接口实现类对象
4.创建Thread类对象,构造函数传递Runnable实践类对象。 a) public Thread(Runnable target):分配一个带有指定目标新的线程对象。
b) public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
5.Thread类对象调用start方法,内部自动调用run方法。
2、代码展示 public class MyTask implements Runnable{ @Override public void run() { //任务逻辑 } } MyTask myTask = new MyTask(); Thread thread = new Thread(myTask); thread.start();
2、(重点!)实现Runnable接口来创建线程的好处
1.避免java中类单继承的局限性
2.降低线程任务对象和线程之间的耦合性 tip:换句话说,我们可以更加专注于线程的任务,先把线程的任务逻辑创建完毕。之后需要执行线程任务的地方就创建线程,执行需要的线程任务即可。并且线程任务可以多次反复使用。有点像零件插拔一样。
(3)匿名内部类方式
1、格式 new 父类/接口(){ //覆盖重写抽象方法 };
2、作用
1.创建父类子类对象的快捷方式
2.创建接口的实现类对象的快捷方式
3、注意事项
1.使用匿名内部类创建的对象只能一次性使用
2.尽量使用lambda表达式进行书写,提高代码可读性和编程效率。
三、(重点!)线程安全问题
(1)出现线程安全的情况
1.有两个以上线程同时操作共享数据
2.操作共享数据的语句有两条以上
3.线程调度是抢占式调度模式。
(2)解决方案 同一个线程,操作共享数据的多条语句全部执行,要么多条语句全部不执行。故而可以使用同步技术。 同步的原理:有锁的线程执行,没有锁的线程等待。
(3)实际解决
1、同步代码块
1.作用:用来解决多线程访问共享数据安全问题
2.格式 synchronized(任意对象){ } 注意事项 :
(1)所有操作共享数据的代码写到同步代码块{}中。
(2)任意对象:任意指的是 类型 可以任意,但要保证全局 唯一 ,被多个线程 共享使用 。 任意对象,也叫锁对象。更加专业的术语:对象监视器。
2、同步方法 格式 修饰符 synchronized 返回值类型 方法名称(参数列表...){ ... } 修饰符 synchronized 返回值类型 方法名称(参数列表...){ ... }
注意事项
(1)所有操作共享数据的代码都在{}中间添加一个
(2)同步方法的锁对象就是this
3、使用Lock接口
1.方法:
1.abstract void lock​() 获得锁。
2.abstract void unlock​() 释放锁。
2.实现类:
java.util.concurrent.locks.ReentrantLock ,空参构造函数
3.注意事项 :
释放锁的动作必须被执行。
(4)实际案例
1、卖票案例分析 (1)总共有3种途径卖票,每个途径,相当于一个线程对象 (2)每个线程对象要执行的任务: 都是在卖票 (3)3个线程对象,操作的资源 100 张票 是被共享的
2、解决策略:
(1) 定义实现类,实现Runnable接口
(2) 覆盖重写Runnable接口中的run方法.指定线程任务——卖票
(2.1)判断是否有票
(2.2)有: 出一张票
(2.3)票的数量减少1
(3) 创建Runnable接口的实现类对象
(4) 创建3个Thread对象,传递Runnable接口的实现类对象,代表,卖票的3种途径
(5) 3个Thread对象分别调用start方法,开启售票
3、代码实现 public class MyTicket implements Runnable{ private int tickets= 100; Object obj = new Object(); @Override public void run() { while (true){ // sellTicketB(); sellTicketA(); } } // 同步函数 private synchronized void sellTicketA(){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + " 卖出第" + tickets-- + "张票"); }else { return; } } //同步进程快 private void sellTicketB() { synchronized(obj){ if(tickets>0){ System.out.println(Thread.currentThread().getName() + " 卖出第" + tickets-- + "张票"); }else { return; } } } } public class synchronizedTest { public static void main(String[] args) { MyTicket task = new MyTicket(); // 三个线程任务来出票 new Thread(task).start(); new Thread(task).start(); new Thread(task).start(); } }
4、线程同步的原理 线程执行的前提:
(1)cpu资源
(2)锁对象 基本规则:
线程对象执行同步代码块中的内容,要么全部执行,要么全部不执行,不能够被其他线程干扰。 拿买票案例举例说明

现在存在t0、t1和t2三个线程。
假设一:
假设t0线程获取cpu资源,执行线程任务遇到同步代码块,判断是否具有锁对象。
有:获取锁对象
进入同步代码块,执行同步代码,,假设t0在执行过程中没有被t1或者t2抢夺cpu资源,那么t0或顺利执行完同步代码块内代码,退出同步代码块,释放锁资源,继续和其他线程抢夺cpu资源和锁对象。
假设二:
假设t0线程获取cpu资源,执行线程任务遇到同步代码块,判断是否具有锁对象
有:获取锁对象
进入同步代码块,执行同步代码,假设t0在执行过程中被t1抢夺了cpu资源,那么t0线程将不能继续执行。t1线程执行任务,遇到同步代码块,判断是否具有锁对象,因为锁已经被t0拿了,因此t1进入阻塞状态,等待获取锁对象被释放。
假设三:
假设t0执行完成了同步代码块的内容,释放了锁对象,t1处于阻塞状态,但此时t2线程抢到了cpu资源,执行代码到同步代码块,然后顺利获取锁对象,进入同步代码块执行。这种情况下,t1将继续等待t2在同步代码块执行完毕,然后再去抢夺cpu资源和锁资源。
可以发现,线程如果不进行调度的管理可能会出现长时间等待的问题,因为抢占式调度具有随机性,不能获得最大的性能。
(持续更新…)
四、线程状态
java.lang.Thread.State 给出了六种线程状态


注意事项(一)
1.sleep方法可以在同步中使用
2.sleep方法可以在非同步中使用
3.sleep方法与锁对象无关(不会释放锁)
注意事项(二)
1.Object类定义wait和notify方法
2.因此任意对象可以调用wait()和notify()方法
3.锁对象可以是任意的
4.但是锁对象必须使用在同步中,因此wait和notify方法必须在同步中使用
案例分析 双线程交替执行。有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,3000}; 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从抽奖池中完成抽奖。 两个线程轮流交替抽奖,每抽出一个奖项就打印出来。 【输出示例】 抽奖箱1...抽出了10元... 抽奖箱2...抽出了20元... 抽奖箱1...抽出了50元... 抽奖箱2...抽出了800元... ... ... 每次抽的过程中,不打印,抽完时一次性打印。 【输出示例】 在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,5,20,50,100,200最高奖项为200元,总计额为385元 在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:500,800,2,80,300,3000最高奖项为3000元,总计额为4682元 在此次抽奖过程中,抽奖项2中产生了最高奖项,该最高奖项为3000元
1. 分析
两个线程的任务都是抽奖,因此很明显只需要定义一个线程任务对象“抽奖”即可。由于两个抽奖箱共享一个奖池,且要求两个抽奖箱交替进行,很明显需要用到线程的等待(wait)和唤醒(notify)操作。因此,两个线程对于奖池中奖金的操作需要同步。前面已经说明,线程任务只有一个,故同步代码块的锁对象使用线程任务对象自身(this)即可。
2、实现思路

3、代码实现 public class RunnableImpl implements Runnable{ private List list; private Map<String,List> mp = new HashMap<>(); private int count = 0; public RunnableImpl(List list) { this.list = list; } @Override public void run() { List subList = new ArrayList(); while (true){ synchronized (this){ if(list.size()<=0){ this.notifyAll(); mp.put(Thread.currentThread().getName(),subList); count++; if(count == 2){ Integer max = 0; String max_name = ""; for (Map.Entry<String, List> entry : mp.entrySet()) { List t = entry.getValue(); String s = entry.getKey(); Integer tmax = 0; Integer sum = 0; StringBuilder sb = new StringBuilder(); for (Object o : t) { sb = sb.append(o).append(","); sum += (Integer)o; tmax = tmax < (Integer)o ? (Integer)o : tmax; } if(max < tmax){ max = tmax; max_name = s; } //sb = sb.deleteCharAt(sb.length()-1); String seq =sb.toString(); System.out.println("在此次抽奖过程中,"+ s +"总共产生了"+t.size()+"个奖项,分别为:" + seq +"最高奖项为"+ tmax +"元,总计额为"+ sum +"元"); } System.out.println("在此次抽奖过程中,"+max_name+"中产生了最高奖项,该最高奖项为"+max+"元"); } break; } if(list.size() > 0){ Object remove = list.remove(new Random().nextInt(list.size())); System.out.println(Thread.currentThread().getName() + "..." + "抽出了"+ remove +"元..."); subList.add(remove); this.notify(); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
热门排行