实现多线程
并发和并行
并行:在同一时刻,有多个指令在多个CPU上同时执行
并发:在同一时刻,有多个指令在单个CPU上交替执行
进程和线程
进程:是正在运行的软件
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡
并发性:任何进程都可以同其他进程一起并发执行
线程:是进程中的单个顺序控制流,是一条执行路径
进程:就是操作系统中正在运行的一个应用程序
线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
实现多线程
Thread类
public class MyThread extends Thread{
@Override
public void run()
{
//代码就是线程在开启之后执行的代码
for(int i=0;i<100;i++)
{
sout("线程开启了"+i);
}
}
}
psvm{
MyThread t1=new MyThread();
t1.start();
MyThread t2=new MyThread();
t2.srart();
}
Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run()
{
for(int i=0;i<100;i++)
{
sout("第二种方式实现多线程"+i);
}}}
//创建一个参数的对象
MyRunnable mr=new MyRunnable();
//创建一个线程对象,并把参数传递给这个线程
//在线程启动之后,执行的就是参数里面的run方法
Thread t1=new Thread(mr);
//开启线程
t1.start();
Callable和Future
public class MyCallable implements Callable<String>
{
@Override
public String call() throws Exception
{
for(int i=0;i<100;i++)
{
sout("哈哈哈哈"+i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}}
public static void main(String [] args)
{
MyCallable mc =new MyCallable();
//可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
FutureTask<String> ft=new FutureTask<>(mc);
//创建线程对象
Thread t1=new Thread(ft);
//开启线程
t1.start();
String s=ft.get();
sout(s)
}
三种方式的对比
public class MyThread extends Thread{
//重写Thread类中的run方法,设置线程任务
@Override
public void run()
{//获取线程名称
String name=getName();
sout(name);
}}
public static void main(String[] args){
MyThread mt=new MyThread();
mt.start();
new MyThread().start();
}
可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread()返回当前正在执行的线程对象的引用
public class MyThread extends Thread{
@Override
public void run()
{ //获取线程名称
//String name =getName();
//sout(name);
/* Thread t=Thread.currentThread();
sout(t);
String name=t.getName();
sout(name); } */
sout(Thread.currentThread().getName());
}
//开启多线程
MyThread mt=new MyThread();
mt.setName("小强");
mt.start();
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name)
{
super(name);
}
@Override
public void run()
{
}}
public class DemoSleep{
public static void main(String [] args)
{//模拟秒表
for(int i=1;i<=60;i++)
{
sout(i);
//使用Thread类的sleep方法让程序睡眠1秒
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}}}}
//创建一个Runnable接口的实现类
public class RunableImpl implements Runnable{
//在实现类中重写Runable接口的run方法,设置线程任务
@Override
public void run()
{
for(int i=0;i<20;i++)
sout(Thread.currentThread().getName());
}}
public static void main(String [] args)
{//创建一个Runnable接口的实现类对象
Runnable run =new RunableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t=new Thread(run);
//调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for(int i=0;i<20;i++)
{
sout(Thread.currentThread().getName());
}}
实现Runnable接口创建多线程程序的好处
1.避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
//创建一个Runnable接口的实现类对象
RunnableImpl run =new RunnableImple();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
//Thread t=new Thread(run);//打印线程名称
Thread t=new Thread(new RunableImpl2());//打印HelloWorld
//调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for(int i=0;i<20;i++)
{
sout(Thread.currentThread().getName());
}
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket=100;
//设置线程任务:卖票
@Override
public void run()
{
//使用死循环,让卖票操作重复执行
while(true)
{//判断票是否存在
if(ticket>0)
{ //票存在,卖票 ticket--
sout(Thread.currentThread().getName());
ticket--;
}}}
//创建Runnable接口的实现类对象
RunnableImpl run=new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,java中提供了同步机制(synchronized)来解决
根据案例简述:
为了保证每个线程都能正常执行原子操作,java引入线程同步机制,怎么去使用呢?
1.同步代码块 2.同步方法 3.锁机制
同步代码块
synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket=100;
//创建一个锁对象
Object obj=new Object();
//设置线程任务:卖票
@Override
public void run()
{
//使用死循环,让卖票操作重复执行
while(true)
{
//同步代码块
synchronized(obj)
{
//先判断票是否存在
if(ticket>0)
{//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException e)
{
e.printStackTrace();
}
//票存在,卖票,ticket--
sout(Thread.currentThread().getName());
ticket--;}}}}}
同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程等着。
public synchronized void method()
{
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
使用同步方法代码如下:
public class Ticket implements Runnable{
private int ticket=100;
//执行卖票操作
@Override
public void run()
{//每个窗口卖票的操作
//窗口 永远开启
}}
private int ticket=100;
//设置线程任务:卖票
@Override
public void run()
{//使用死循环,让卖票操作重复执行
while(true)
{
payTicket();
}
}
public synchronized void payTicket()
{//先判断票是否存在
if(ticket>0)
{//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}//票存在,卖票 ticket--
sout(Thread.currentThread().getName);
ticket--;
} }
//模拟卖票案例
//创建3个线程,同时开启,对共享的票进行出售
//创建Runnable接口的实现类对象
RunnableImpl run =new RunnableImp();
sout(run);
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
死锁
public static void main(String [ ] args ){
Object objA=new Object();
Object objB=new Object();
new Thread(()->{
while(true){
synchronized(objA){
synchronized(objB){
sout("小康正在走路");}}}}).start();
new Thread(()->{
while(true){
synchronized(objB){
synchronized(objA){
sout("小薇在走路");
}
} }
}
).start();}}
卖票出现了问题
- 相同的票出现了多次
- 出现了负数的票
- 问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
- 如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
- 怎么实现呢?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
生产者和消费者
等待和唤醒的方法
public class Desk{
//定义一个标记
//true就表示桌子上有汉堡包的,此时允许吃货执行
//flase就表示桌子上没有汉堡包的,允许厨师执行
public static boolean flag=false;
//汉堡包的总数量
public static int count=10;
//锁对象
public static final Object lock=new Object();
}
public void class Foodie(){
while(true){
synchronized(Desk.lock){
if(Desk.count==0){//判断是否有汉堡包,没有就跳出线程
break;}
else{
if(Desk.flag)
{//有
sout("吃货在吃汉堡");
Desk.flag=false;
Desk.lock.notifyAll();
Desk.count--;
}else{//没有就等待
//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法。
try{
Desk.lock.wait();
}catch(InterruptedException e)
{
e.print
}
}
}}}}
public class Cooker extends Thread{
//生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才产生
2.把汉堡包放在桌子上
3.叫醒等待的消费者开吃
@Override
public void run()
{
while(true){
synchronized(Desk.lock)
{
if(Desk.count==0)
{break;}
else{
if(!Desk.flag){
//生产
sout("厨师正在生产汉堡包");
Desk.flag=true;
Desk.lock.notifyAll();
}else{
try{
Desk.lock.wait();
}catch(InterruptedException e){
e.printStavkTrace();
}}}}}}
public class Demo
{ public static void main(...)
{//生产者步骤:
/*
1.判断桌子上是否有汉堡
如果有就等待,如果没有才生产
2.把汉堡放在桌子上
3.叫醒等待的消费者开吃 */
Foodie f=new Foodie();
Cooker c=new Cooker();
f.start();
c.start();
}}
套路
1.while(true)死循环
2.synchronized 锁,锁对象要唯一
3.判断,共享数据是否结束。 结束
4.判断,共享数据是否结束。没结束
生产者和消费者代码改写
阻塞队列实现等待唤醒机制
BlockingQueue的核心方法:
put(anObject):将参数放入队列,如果放不进去会阻塞
take():取出第一个数据,取不到会阻塞
常见BlockingQueue:
ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无解。但不是真正的无界,最大为int的最大值
//阻塞队列的基本用法
//创建阻塞队列的对象,容量为1
ArrayBlockingQueue<String> arrayBlockingQueue=new ArrayBlockingQueue<>(1);//容量为1
//存储元素
arrayBlockingQueue.put("汉堡包");
//取元素
sout(arrayBlockingQueue.take());
//下面那个取不到
sout(arrayBlockingQueue.take());
sout("程序员结束了......");
阻塞队列和等待唤醒机制相结合
//创建一个阻塞队列,容量为1
ArrayBlockingQueue<String> list =new ArrayBlockingQueue<>(1);
//创建线程并开启
Cooker c=new Cooker(list);
Foodie f=new Foodie(list);
public class Cooker extends Thread{
private ArrayBlockingQueue<String> list;
public Cooker(ArrayBlockingQueue<String> list){
this.list=list;
}
@Override
public void run()
{ while(true){
try{
list.put("汉堡包");
sout("厨师放了一个汉堡包");
}catch(InterruptedException e)
{
e.printStackTrace(); }}
}
}
public class Foodie extends Thread{
private ArrayBlockingQueue<String> list;
public Foodie(ArrayBlockingQueue<String> list){
this.list=list;
}
@Override
public void run()
{
while(true){
try{
String take=list.take();
sout("吃货从队列中获取了"+take);
}catch(InterruptedException e)
{
e.printStackTrace();
}}}}
多线程高级
虚拟机中线程的六种状态
线程池
//创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值
ExceutorService executorService = Executors.newCachedThreadPool();
//Executors --可以帮助我们创建线程池对象
//ExecutorService--可以帮我们控制线程池
executorService.submit( ()->{
sout(Thread.currentThread().getName());
});
Thread.sleep(2000);
executorService.submit(()->{
sout(Thread.currentThread().getName());
});
executorService.shutdown();
//pool-1-thread-1
//pool-1-thread-1
//创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值
ExceutorService executorService = Executors.newCachedThreadPool();
//Executors --可以帮助我们创建线程池对象
//ExecutorService--可以帮我们控制线程池
executorService.submit( ()->{
sout(Thread.currentThread().getName());
});
executorService.submit(()->{
sout(Thread.currentThread().getName());
});
executorService.shutdown();
//pool-1-thread-2
//pool-1-thread-1
区别: 在于两任务同时运行而第一个任务运行没来的及将线程归还给池子所以产生2线程,而加了睡眠归还了线程复用第一个线程所以只有一个线程
ThreadPoolExecutor pool =new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
public class MyRunnable implements Runnable{
@Override
public void run()
{
sout(Thread.currentThread().getName()+"在执行了");
}
}
任务拒绝策略
ThreadPoolExecutor pool =new ThreadPoolExecutor(
2,5,2,TimeUnit.HOURS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for(int i=1;i<=15;i++)
{ pool.submit(new MyRunnable());
}
pool.shutdown();
);
TCP通信程序
public class Money{
public static int money=100000;
}
public class MyThread1 extends Thread{
@Override
public void run()
{
while(Money.money==10000)
{}
sout("结婚基金");
}}
public class MyThread2 extends Thread{
@Override
public void run()
{try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
Money.money=90000;
}}
psvm(){
MyThread1 t1=new MyThread1();
t1.setName("小路同学");
t1.start();
MyThread2 t2=new MyThread2();
t2.setName("小明同学");
t2.start();
}
女孩无法获得最新男孩数据一直死循环
volatile
如果A线程修改了堆中共享变量的值,那么其他线程不一定能及时使用最新的值
Volatile关键字:强制线程每次在使用的时候,都会看一下共享区域最新的值
public class Money{
public static volatile int money=100000;
}
同步代码块锁对象
public class Money{
public static Object lock=new Object();
Public static volatile int money = 1000000;
}
public class MyThread1 extends Thread{
@Override
public void run()
{
while(true)
{ synchronized(Money.lock)
{
if(Money.money!=100000)
{ sout("结婚基金已经不是十万了"); break;
}
} }}}
public class MyThread2 extends Thread{
@Override
public void run(){
synchronized(Money.lock){
try{
Thread.sleep(10);}
catch(InterruptedException e){
e.printStackTrace();
}
Money.money=90000;
}}}
Synchronized同步代码块
1.线程获得锁 2.清空变量副本 3.拷贝共享变量最新的值到变量副本中
4.执行代码 5.将修改后变量副本中的值赋值给共享数据 6.释放锁
原子性
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会接受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体
public class MyAtomThread implements Runnable {
private int count =0;//送冰淇凌的数量
@Override
public run()
{ for(int i=0;i<100;i++){
//1.从共享数据中读取数据到本线程中
//2.修改本线程栈中变量副本的值
//3.会把本线程栈中变量副本的值赋值给共享数据
count++;
sout("送冰淇凌的数量"+count);
}}}
cout++不是一个原子性操作,也就是说他在执行的过程中 ,有可能被其他线程打断操作
psvm(){
MyAtomThread atom=new MyAtomThread();
for(int i=0;i<100;i++){
new Thread(atom).start();
}
}
volatile 关键字: 只能保证线程每次在使用共享数据的时候是最新值
但是不能保证原子性
public class MyAtomThread implenments Runnable{
private volatile int count=0;
private Object lock =new Object();
@Override
public void run()
{
for(int i=0;i<100;i++){
synchronized(lock) //同步代码块解决了原子问题
{ count++;
sout("已经送了"+count+"冰淇凌"); }
}}}
原子类AtomicInteger
AtomicInteger ac =new AtomicInteger();
sout(ac);//0
AtomicInteger ac2=new AtomicInteger(10);
sout(ac2);//10
public static void main(String[] args){
AtomicInteger ac1=new AtomicInteger(10);
sout(ac1);//10
AtomicInteger ac2=new AtomicInteger(10);
int andIncrement = ac2.getAndIncrement();
sout(andIncrement);//10
sout(ac2.get());//11
AtomicInteger ac3=new AtomicInteger(10);
int i=ac3.incrementAndGet();
sout(i);//自增后的值
sout(ac3.get());//11
AtomicInteger ac5=new AtomicInteger(10);
int andSet=ac5.getAndSet(20);
sout(andSet);//10
sout(ac5.get());//20
}
public class MyAtomThread implenments Runnable{
AtomicInteger ac=new AtomicInteger(0);
@Override
public void run()
{
for(int i=0;i<100;i++){
// synchronized(lock) //同步代码块解决了原子问题
{
int count =ac.incrementAndGet();
sout("已经送了"+count+"冰淇凌"); }
}}}
AtomicInteger原理
自旋锁+CAS算法
CAS算法:有3个操作数(内存值V,旧的预期值A,要修改的值B)
当旧的预期值A==内存值 此时修改成功,将V改为B
当旧的预期值A!=内存值 此时修改失败,不做任何操作
并重新获取现在的最新值(这个重新获取的动作就是自旋)
重新获取现在的最新值(这个重新获取的动作就是自旋)
synchronized和CAS的区别
相同点:在多线程情况下,都可以保证共享数据的安全性
不同点:
synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。
所以在每次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值
如果别人没修改过,那么我现在直接修改共享数据的值.(乐观锁)
public class MyHashMapDemo2 {
public static void main(String[] args) throws InterruptedException {
HashMap<String, String> hm = new HashMap<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println();
System.out.println("_____________________");
//为了t1和t2能把数据全部添加完毕
Thread.sleep(1000);
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i+""));
}}}
并发工具类
Hashtable
HashMap是线程不安全的(多线程环境下可能会存在问题)
为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下
Hashtable采取悲观锁synchronized的形式保证数据的安全性
public class MyHashMapDemo2 {
public static void main(String[] args) throws InterruptedException {
Hashtable<String, String> hm = new Hashtable<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println();
System.out.println("_____________________");
//为了t1和t2能把数据全部添加完毕
Thread.sleep(1000);
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i+""));
}}}
ConcurrentHashMap
public class MyHashMapDemo2 {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println();
System.out.println("_____________________");
//为了t1和t2能把数据全部添加完毕
Thread.sleep(1000);
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i+""));
}}}
小结
1.HashMap是线程不安全的。多线程环境下会有数据安全问题
2.Hashtable是线程安全的,但是会将整张表锁起来,效率低下
3.ConcurrentHashMap也是线程安全的,效率较高
CountDownLatch
使用场景:让某一条线程等待其他线程执行完毕之后再执行
public class MyCountDownLatchDemo {
public static void main(String[] args) {
//创建CountDownLatch的对象,需要传递给四个线程
//参数传等待线程的数量,本案例等待案例是3个
//在底层定义一个计数器,此时计数器值就是3
CountDownLatch countDownLatch=new CountDownLatch(3);
//创建4个线程对象并开启他们
MotherThread motherThread=new MotherThread(countDownLatch);
motherThread.start();
Chilethread1 t1=new Chilethread1(countDownLatch);
t1.setName("小明");
Chilethread2 t2=new Chilethread2(countDownLatch);
t1.setName("小红");
Chilethread3 t3=new Chilethread3(countDownLatch);
t1.setName("小刚");
t1.start();
t2.start();
t3.start();
}
}
public class Chilethread1 extends Thread {
private CountDownLatch countDownLatch;
public Chilethread1(CountDownLatch countDownLatch) {
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
//吃饺子
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "在吃第" + i + "个饺子");
}
//吃完说一声
//每一次countDown方法时候,就让计数器-1
countDownLatch.countDown();
}
}
public class MotherThread extends Thread{
private CountDownLatch countDownLatch;
public MotherThread(CountDownLatch countDownLatch) {
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
//1.等待
try {
//当计数器变成0的时候,会自动唤醒这里等待的线程
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.收拾碗筷
System.out.println("妈妈在收拾碗筷");
}
}
CountDownLatch小结
使用场景:让某一条线程等待其他线程执行完毕之后再执行
CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。
await():让线程等待,当计数器为0时,会唤醒等待的线程
countDown():线程执行完毕时调用,会将计数器-1。
Semaphore
使用场景:可以控制访问特定资源的线程数量
public class MyRunable implements Runnable{
//获得管理员对象
private Semaphore semphore=new Semaphore(2);
@Oveerride
public void run(){
//获得通行证
try{
semaphore.acquire();
//开始行驶
System.out.println("获得通行证");
Thread.sleep(2000);
System.out.println("归还通行证");
//归还通行证
semaphore.release();
}catch(InterruptedException e){
e.printStackTrace();
} }}
Exceutors工具类获取线程池对象