同步以及线程安全:
同步的好处:
解决了多线程的安全问题
同步的弊端:
加入同步之后,就相当于加了一把锁,这样每次进入同步代码块的时候都会判断一下,这样无形之中,降低了程序运行的效率。
判断同步时的对象:
1、同步代码块的锁对象是谁?
任意对象,多个线程之间的锁对象要是唯一的。
2、同步方法所判断的锁对象是谁?
当前对象this
3、同步静态方法所判断的锁对象是谁?
当前线程类的对象,(.class)
解决同步安全问题的方案2:
--使用Lock锁
注意:多个线程对象拥有的锁对象要一致
死锁问题:
两个或者两个以上的线程在争夺资源的过程中,出现了相互等待的现象,这个现象称之为死锁现象
举例:
中国人和外国人吃饭的案例:
正常情况下:
中国人:两支筷子
外国人:一把刀,一把叉
死锁的情况:
中国人:一支筷子,一把刀
外国人:一支筷子,一把叉
分析线程间通信的案例:
共有资源类:Student
生产者:SetThread 给学生对象进行赋值
消费者:GetThread 给学生对象进行取值
测试类:StudentDemo
问题1:
按照我们分析类思路来写程序,运行后发现每一次运行的结果都是null--0
这是必然,因为我们在每一个线程类中都单独new了一次学生对象,而我们实际上赋值和取值的对象应该是同一个。所以不应该在内部定义。
怎么解决呢?
应该在外部把学生对象定义出来,然后使用构造方法的形式进行传参。
问题2:
我们为了运行出来的数据好看一点,在程序加入了循环和判断,赋值的时候,赋不同的值。
但是呢,在运行的时候,又出现了新的问题。
1、姓名和年龄不匹配
2、同一个数据出现了多次(消费者消费了多次)
原因:
--1)、姓名和年龄不匹配
这是由线程执行具有随机性导致的
--2)、同一个数据出现了多次(消费者消费了多次)
由于CPU一个小小的时间片就足够我们线程执行很多次
线程安全的问题:
1、是否有多线程环境 --是 一个生产者,一个消费者
2、是否有共享数据 --是 Student
3、是否有多条语句操作着共享数据 --是
既然都满足,那么就说明有线程安全问题。
解决方案:
--加锁。
注意:
1、不同种类的线程类中都要加锁
2、并且不同种类的线程类中的锁对象必须是同一个(指的是线程对象共同拥有一个锁对象)
问题3:等待唤醒机制。我们用有限的技术来解决问题2的时候,只解决了数据不一致的问题,但是没有解决重复出现的问题。经过分析后,发现这是生产者消费者的问题。要使用新的技术来实现。如何添加等待唤醒机制呢?
Object类中有三个方法用来解决生产者消费者问题:
void wait()
导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
void notify()
唤醒正在等待对象监视器的单个线程。
void notifyAll()
唤醒正在等待对象监视器的所有线程。
为什么上面这些方法不定义在Thread类中呢?
这些方法要想被调用,就必须通过锁对象调用。多个线程对象中调用等待唤醒的对象要一致,并且与锁对象一致。并且通过测试后发现,锁对象的类型可以是任意类型的。
所以这些方法会被定义在Object类中,因为java中所有的类都有一个父类叫做Object,他们创建出来的对象将来也有可能被作为锁对象。
线程组:
ThreadGroup:
线程组代表一组线程。此外,线程组还可以包括其他线程组。
理解:把多个线程组合到一起,可以对这一组线程进行管理。
//创建Runnable对象
//借助Thread类创建线程对象
//想办法获取线程所在线程组
ThreadGroup getThreadGroup()
//返回此线程所属的线程组。
ThreadGroup group1 = t1.getThreadGroup();
//默认情况下,如果不对线程进行分组,会有一个默认的组,线程默认所属线程组叫做main,也就是都属于主线程组中的
String getName() 返回此线程组的名称。
!//没有办法做到分完组后,再进行修改组,所以需要构造一个新的线程组。创建线程组的同时给线程组起名字
ThreadGroup group = new ThreadGroup("小白组");
//创建线程对象,并分配组
Thread t1 = new Thread(group, myRunnable, "小白1号");
//打印线程所属组的名字
String s1 = t1.getThreadGroup().getName();
//直接通过线程组名来设置守护线程
group.setDaemon(true);
线程池:
线程池:
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
如何实现线程池程序呢?
1、创建线程池对象,JDK5新增了一个Executors工厂类来产生线程池,有如下几个静态方法:
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
本日笔记以第二个线程池的使用举例
2、如何往线程池中放线程?(可以放哪种线程)
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
--((2)代表该线程池容量为2)
//Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来。
3、怎么运行
当提交到线程池的时候,就会自动启动一个线程执行
//线程池需要手动关闭
//启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
//void shutdown() --关闭线程池
创建线程的第三种方式:
<T> Future<T> submit(Callable<T> task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
创建线程的第三种方式,需要结合线程池一起使用:
面试题:java种实现线程的方式有几种
--回答:
1、有两种,一种是继承Thread类,重写run方法,使用start启动线程;
2、另一种是实现Runnable接口,实现run方法,借助Thread类创建线程对象,使用start方法启动;
3、其实还有一种方式实现,实现Callable接口,实现call方法,需要结合线程池的方式创建线程对象,提交到线程池执行。