实现多线程的时候:
1、需要继承Thread类
2、必须要重写run方法,指的是核心执行的逻辑
3、线程在启动的时候,不要直接调用run方法,而是要通过start()来进行调用
4、每次运行相同的代码,出来的结果可能不一样,原因在于多线程谁先抢占资源无法进行人为控制
第二种实现方式:使用了代理设计模式(启动线程方式不同)
1、实现Runnable接口
2、重写run方法
3、创建Thread对象,将刚刚创建好的runnable的子类实现作为thread的构造参数
4、通过thread.start()进行启动
两种实现方式哪种用的比较多
推荐使用第二种方式,
1、java是单继承,将继承关系留给最需要的类
2、使用runnable接口之后不需要给共享变量添加static关键字,每次创建一个对象,作为共享对象即可
见下边的例子:
public class TicketThread extends Thread{
private static int ticket = 5;
@Override
public void run() {
for(int i = 0;i<100;i++){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
}
public static void main(String[] args) {
TicketThread t1 = new TicketThread(); //创建了五个子类对象,共享变量ticket 会出现问题
TicketThread t2 = new TicketThread();
TicketThread t3 = new TicketThread();
TicketThread t4 = new TicketThread();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
public class TicketRunnable implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
}
public static void main(String[] args) {
TicketRunnable ticket = new TicketRunnable(); //只创建了一次子类对象,只用过一次ticket
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
线程的生命周期:
1、新生状态:
当创建好当前线程对象之后,没有启动之前(调用start方法之前)
ThreadDemo thread = new ThreadDemo()
RunnableDemo run = new RunnableDemo()
2、就绪状态:准备开始执行,并没有执行,表示调用start方法之后
当对应的线程创建完成,且调用start方法之后,所有的线程会添加到一个就绪队列中,所有的线程同时去抢占cpu的资源
3、运行状态:当当前进程获取到cpu资源之后,就绪队列中的所有线程会去抢占cpu的资源,谁先抢占到谁先执行,在执行的过程中就叫做运行状态抢占到cpu资源,执行代码逻辑开始
4、死亡状态:当运行中的线程正常执行完所有的代码逻辑或者因为异常情况导致程序结束叫做死亡状态
进入的方式:
1、正常运行完成且结束
2、人为中断执行,比如使用stop方法
3、程序抛出未捕获的异常
5、阻塞状态:在程序运行过程中,发生某些异常情况,导致当前线程无法再顺利执行下去,此时会进入阻塞状态,进入阻塞状态的原因消除之后,所有的阻塞队列会再次进入到就绪状态中,随机抢占cpu的资源,等待执行
进入的方式:
sleep方法
等待io资源
join方法(代码中执行的逻辑)
Thread API
* 介绍线程类api方法
* */
public class ThreadApiDemo implements Runnable{
public static void main(String[] args) {
//获取当前线程对象
Thread thread = Thread.currentThread();
//获取当前线程的名称
System.out.println(thread.getName());
//获取线程的id
System.out.println(thread.getId());
//获取线程的优先级,在一般系统中范围是0-10的值,如果没有经过设置的话,就是默认值5,有些系统是0-100
System.out.println(thread.getPriority());
//设置线程池的优先级
/*
* 优先级越高一定越先执行吗?
* 不一定,只是优先执行的概率比较大而已
* */
thread.setPriority(6);
System.out.println(thread.getPriority());
ThreadApiDemo threadApiDemo = new ThreadApiDemo();
Thread t1 = new Thread(threadApiDemo);
System.out.println(t1.isAlive());
t1.start();
System.out.println(t1.isAlive());
System.out.println(t1.getPriority());
// for(int i = 0;i<5;i++){
// System.out.println(Thread.currentThread().getName()+"-----"+i);
// }
System.out.println(t1.isAlive());
}
@Override
public void run() {
// for(int i = 0;i<5;i++){
// System.out.println(Thread.currentThread().getName()+"-----"+i);
// }
}
}
join,sleep,yield-----三种可以暂停Thread的方法:
public class MyRun implements Runnable {
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"------"+i);
}
}
}
//join
public class JoinTest {
public static void main(String[] args) {
MyRun run = new MyRun();
Thread thread = new Thread(run);
thread.start();
for(int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"-----------------"+i);
if(i==3){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//sleep
public class SleepTest {
public static void main(String[] args) {
MyRun run = new MyRun();
Thread thread = new Thread(run);
thread.start();
for(int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"=============="+i);
if(i==2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//yield
public class YieldTest {
public static void main(String[] args) {
MyRun run = new MyRun();
Thread thread = new Thread(run);
thread.start();
for(int i = 0;i<5;i++){
if(i==2){
Thread.yield();
// thread.stop();
System.out.println(Thread.currentThread().getName()+"=============="+i+"礼让一次");
}else{
System.out.println(Thread.currentThread().getName()+"=============="+i);
}
}
}
}
经典案例:
public class Test1 implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"====="+i);
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread = new Thread(test1);
thread.start();
for(int i=10;i>0;i--){
System.out.println(Thread.currentThread().getName()+"----"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
在多线程的时候,可以实现唤醒和等待的过程,但是唤醒和等待操作的对应不是thread类
而是我们设置的共享对象或者共享变量(Object的方法)
同步
多线程并发访问的时候会出现数据安全问题:----防止同一时刻多个线程访问
解决方式:
1、同步代码块
synchronized (共享资源、共享对象,需要是object的子类) {具体执行的代码块}
共享资源,如总票数5,用runnable接口就不需要将其定义为static,用thread类就需要定义为static
2、同步方法
将核心的代码逻辑定义成一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象
加锁之后,保证被synchronized修饰的方法或者代买块在任意时刻只能有一个线程进行执行。
举例,依旧以上文取票为例:
//同步代码块,要指定锁对象
public class TicketRunnable2 implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) { //等待放在锁的外面,循环的里边,让其他线程有公平的抢占内存的机会,在这个线程睡的时候其他线程有机会抢占内存
e.printStackTrace();
}
synchronized (this){ //上锁,()要放一个Object对象
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
}
}
public static void main(String[] args) {
TicketRunnable2 ticket = new TicketRunnable2();
Thread t1 = new Thread(ticket,"A");
Thread t2 = new Thread(ticket,"B");
Thread t3 = new Thread(ticket,"C");
Thread t4 = new Thread(ticket,"D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//同步方法---不需要指定锁对象
public class TicketRunnable3 implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.sale();
}
}
/*
* 使用同步方法解决多线程数据安全的问题
* */
public synchronized void sale() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
public static void main(String[] args) {
TicketRunnable3 ticket = new TicketRunnable3();
Thread t1 = new Thread(ticket, "A");
Thread t2 = new Thread(ticket, "B");
Thread t3 = new Thread(ticket, "C");
Thread t4 = new Thread(ticket, "D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
volatile关键字(背)
保证线程可见性
禁止指令重排序
(可见性:修改不能被另外一个线程及时看到,对这个变量加了volatile后,就可以一个线程对其改变,另外一个线程马上就能看到—缓存一致性协议MESI)
CAS 无锁优化(自旋锁)(Atomick开头的,都是基于CAS来保证线程安全的)
Compare And Swap
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
CAS使用目的:
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
三、CAS存在的问题
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
- ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
-
循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生死锁:
生产者消费者模式
产品:共享资源
pc版:
/*
*消费者
* 从共享空间中取走产品
* */
public class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者取走了"+this.goods.getBrand()+"----"+this.goods.getName());
}
}
}
public class Goods {
private String brand;
private String name;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
* 生产产品,将产房放置到共享空间中
*
* */
public class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
goods.setBrand("娃哈哈");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
goods.setName("矿泉水");
} else {
goods.setBrand("旺仔");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
goods.setName("小馒头");
}
System.out.println("生产者生产了" + this.goods.getBrand() + "--" + this.goods.getName());
}
}
}
/*
-
多线程访问的时候出现了数据安全的问题
-
1、生产者没有生产商品,消费者就可以获取
-
2、商品的品牌和名称对应不上
-
*/
public class Test {
public static void main(String[] args) {Goods goods = new Goods(); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); Thread t1 = new Thread(producer); Thread t2 = new Thread(consumer); t1.start(); t2.start();
}
}
pc2解决了品牌、名称对应不上的问题
*
* 从共享空间中取走产品
* */
public class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
goods.get();
}
}
}
public class Goods {
private String brand;
private String name;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//消费者获取商品
public synchronized void get(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
}
//生产者生产商品
public synchronized void set(String brand,String name){
this.setBrand(brand);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
}
}
public class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
goods.set("娃哈哈","矿泉水");
} else {
goods.set("旺仔","小馒头");
}
}
}
}
public class Test {
public static void main(String[] args) {
Goods goods = new Goods();
Producer producer = new Producer(goods);
Consumer consumer = new Consumer(goods);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
pc3解决没有产品消费者就消费了的情况
* 从共享空间中取走产品
* */
public class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
goods.get();
}
}
}
public class Goods {
private String brand;
private String name;
//默认是不存在商品的,如果值等于true的话,代表有商品
private boolean flag = false;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//消费者获取商品
public synchronized void get(){
/*
* 如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态,等待生产者生产,当
* 有商品之后,再开始消费
* */
if (!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
flag = false;
//唤醒生产者去进行生产
notify();
}
//生产者生产商品
public synchronized void set(String brand,String name){
//当生产者抢占到cpu资源之后会判断当前对象是否有值,如果有的话,以为着消费者还没有消费,需要提醒消费者消费,同时
//当前线程进入阻塞状态,等待消费者取走商品之后,再次生产,如果没有的话,不需要等待,不需要进入阻塞状态,直接生产即可
if(flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setBrand(brand);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
//如果代码执行到此处,意味着已经生产完成,需要将flag设置为true
flag = true;
//唤醒消费者去进行消费
notify();
}
}
public class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
goods.set("娃哈哈","矿泉水");
} else {
goods.set("旺仔","小馒头");
}
}
}
}
public class Test {
public static void main(String[] args) {
Goods goods = new Goods();
Producer producer = new Producer(goods);
Consumer consumer = new Consumer(goods);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
pc4用JUC实现上边的功能(Java util concurrent 解决多线程等待和唤醒的功能),而且不再是生产者生产一个消费者消费一个,用到了队列
public class ConsumerQueue implements Runnable {
private BlockingQueue<Goods> blockingQueue;
public ConsumerQueue(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
Goods goods = blockingQueue.take();
System.out.println("消费者消费的商品是:"+goods.getBrand()+"--"+goods.getName());
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Goods {
private String brand;
private String name;
public Goods(String brand, String name) {
this.brand = brand;
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ProducerQueue implements Runnable {
private BlockingQueue<Goods> blockingQueue;
public ProducerQueue(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
Goods goods = null;
if(i%2==0){
goods = new Goods("娃哈哈","矿泉水");
}else{
goods = new Goods("旺仔","小馒头");
}
System.out.println("生产者开始生产商品:"+goods.getBrand()+"--"+goods.getName());
try {
blockingQueue.put(goods);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import com.mashibing.pc.Producer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Test {
public static void main(String[] args) {
BlockingQueue<Goods> queue = new ArrayBlockingQueue<Goods>(5);
ProducerQueue producerQueue = new ProducerQueue(queue);
ConsumerQueue consumerQueue = new ConsumerQueue(queue);
new Thread(producerQueue).start();
new Thread(consumerQueue).start();
}
}