说了线程的基础之后。相信大家都应该对线程有一点的了解了,现在我们来讨论一些关于线程更深层次的问题。
我先和大家讨论一个问题啊,如果,我用一个线程(Thread1)向一个字符串(String s=”阿泽”)拼接“中二病”,再用另一个线程(Thread2)箱这个字符串s拼接“mdzz”.这时候会发生什么呢?
假如说Thread1先得到CPU分配的时间片,Thread1先执行,在像字符串“阿泽”拼接了一个“中”字符后,CPU分配给Thread1的时间恰好耗尽(假如啊,只是假如,不一定就这么巧),Thread1进入Runnable状态,等待下次时间分片再次分配。这个时候CPU时间分片又将CPU时间分配给Thread2,这个时候Thread2又开始进入Running状态,Thraed2又开始给字符串“阿泽中”开始拼接了,当拼接了“mdz”之后,时间恰好耗尽了。Thread1又开始工作了,又给字符串“阿泽中mdz”拼接“二”。Thread1工作完成后就去世了(Dead),Thread2把没有拼接完的部分继续拼接完。最后这个字符串变成了“阿泽中mdz二z”。
因为CPU分配给线程的时间片是不固定的,这样不就导致每次我想要用这两个线程去拼接的时候每次结果都不一样了嘛。那这种情况应该怎么处理啊。
这就是多线程竞争同一资源的问题。在说这个问题的时候,就又要来说说概念了。
同步:我们汉语中,同步的意思就是同时做某个事,这两件事的状态 是一样的。但是在线程中这个意思又是和汉语中的意识是不一样的。我们在前面提到过,并发是伪“同时”,这都已经有“同时‘’了,那么同步肯定不是“同时”。在线程中内,“同步”是指多线程同时访问同一资源,其他线程等待正在访问资源的线程访问结束后,再访问。这样会造成线程浪费时间,降低工作效率 ,但是在对资源进行增删改操作时更为安全。
说完了同步,肯定有些好奇宝宝要问啦。这个既然都有“同步”了,那是不是还有“异步”呢?
答案是没有,在线程中,并没有“异步”这个概念。
不仅如此,在不同的领域,“同步”,“异步”都有不同的说法。
在过程调用以及访问服务器的领域,这里的异步是指在调用一个过程或请求服务器的服务时,调用/请求方的调用/请求可以在调用操作完成或服务器响应之前返回,做一些其他的工作,当调用完成或服务器响应时再继续与被调方/服务器的协同工作。而同步则是在调用操作完成或服务器响应之前不返回,持续地等待,以确保调用方/客户端与被调方/服务器协同一致。
另外在通信领域也有同步/异步的概念,异步双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,结束时有停止位。而同步就是接收端要按照发送端所发送的每个码元的起止时刻和重复频率来接收数据,两者时间上必须取得一致。
在操作系统中在过程调用以及访问服务器的领域,这里的异步是指在调用一个过程或请求服务器的服务时,调用/请求方的调用/请求可以在调用操作完成或服务器响应之前返回,做一些其他的工作,当调用完成或服务器响应时再继续与被调方/服务器的协同工作。而同步则是在调用操作完成或服务器响应之前不返回,持续地等待,以确保调用方/客户端与被调方/服务器协同一致。
另外在通信领域也有同步/异步的概念,异步双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,结束时有停止位。而同步就是接收端要按照发送端所发送的每个码元的起止时刻和重复频率来接收数据,两者时间上必须取得一致。
“同步”,“异步”还在很多不同的地方有不同的意义,有兴趣深入研究的小伙伴可以去研究研究,然后一定不要忘记和博主讨论讨论你的研究结果哦。
既然说到“同步”的概念了,再让我们来看看java中是通过什么来实现的。
synchronized关键字
synchronized关键字只有两种用法:
一.sychronized修饰方法:
/**
* 多线程并发安全问题
* 当多个线程访问同一资源时,由于线程切换的不确定性,
* 可能导致执行代码逻辑上的混乱。严重时可能导致系统崩溃
*
* @author Analyze
*
*/
public class SyncDemo1 {
public static void main(String[] args) {
final Table table=new Table();
Thread t1=new Thread(){
public void run(){
while(true){
int bean=table.getBean();
//模拟cpu时间耗尽,线程发生切换
Thread.yield();
System.out.println(getName()+" :"+bean);
}
}
};
Thread t2=new Thread(){
public void run(){
while(true){
int bean=table.getBean();
//模拟cpu时间耗尽,线程发生切换
Thread.yield();
System.out.println(getName()+" :"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
//桌子上有20个豆子
private int beans=20;
/**
* 当一个方法被synchronized修饰后,该方法称为“同步方法”,意思是说当前方法不能同时被两个以上的线程执行。
*
* 多线程并发安全问题的本质就是多个线程发生了“抢”的情况。解决的方法就是排队干
*
* 同步方法由于不允许多个线程同时执行方法内容所以解决了线程之间抢的问题
* @return
*/
public synchronized int getBean(){//synchronized 同步的
if(beans==0){
throw new RuntimeException("没有豆子了");
}
//模拟cpu时间耗尽,线程发生切换
Thread.yield();
return beans--;
}
}
二.synchronized 块
package se.day10;
/**
* 有效缩小同步范围可以在保证并发安全的前提下提高并发效率
* @author Analyze
*
*/
public class SyncDemo2 {
public static void main(String[] args) {
final Shop s=new Shop();
Thread t1=new Thread(){
public void run(){
s.buy();
}
};
Thread t2=new Thread(){
public void run(){
s.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
// public synchronized void buy(){
public void buy(){
try {
Thread t=Thread.currentThread();
System.out.println(t+"正在挑选衣服。。。");
Thread.sleep(5000);
/*
*同步块需要制定同步监视器(上锁的对象)
*若希望该同步块有效果,必须保证多个线程看的锁对象相同
*通常使用this即可
*/
synchronized(this){
System.out.println(t+"正在试衣服。。。");
Thread.sleep(5000);
}
System.out.println(t+"结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这里解释一下。我们把线程想像成人,同时不能枪战的资源就是衣店的试衣间。那么如果在buy上锁(synchronized),意味着,这个商店每进一个人就直接把店门关了。而使用synchronized块意味着进去一个人,不再上锁商店门,而是只上锁试衣间。这样就可以提高很多效率了。
我们再来看看在静态方法上枷锁会怎么样
package se.day10;
/**
* 静态方法的同步
* @author Analyze
*
*/
public class SyncDemo3 {
public static void main(String[] args) {
final SyncDemo3 sd1=new SyncDemo3();
final SyncDemo3 sd2=new SyncDemo3();
Thread t1=new Thread(){
public void run(){
sd1.dosome();
}
};
Thread t2=new Thread(){
public void run(){
sd2.dosome();
}
};
t1.start();
t2.start();
}
/**
* 成员方法上使用synchronized后,锁对象为this,即方法所属对象
*
* 静态方法上使用synchronized后,锁对象为当前类的类对象
*
* 类对象:即class类的一个实例
*
* 当jvm加载我们使用的一个类时,首先读取该类的class文件,然后创建一个class类的实例描述当前类的信息。
* 每个类都是有且只有一个class类的实例对应。静态方法上锁的就是该对象。所以同步的静态方法一定具有同步效果
*/
public synchronized static void dosome(){
Thread t=Thread.currentThread();
System.out.println(t+"正在执行dosome方法");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(t+"执行dosome方法完毕");
}
}
因为静态方法时属于类的,所有对象共用静态方法,所以静态方法枷锁一定是同步的(不同线程访问不同对象的静态块也会同步)。
我们再来看看给同一对象不同方法加上syncronized。
package se.day10;
/**
* 互斥锁
* synchronized修饰的是两段代码,但是锁对象相同
* 那么这两段代码就是互斥的
* @author Analyze
*
*/
public class SyncDemo4 {
public static void main(String[] args) {
final Foo f=new Foo();
Thread t1=new Thread(){
public void run(){
f.mathodA();
}
};
Thread t2=new Thread(){
public void run(){
f.mathodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void mathodA(){
Thread t=Thread.currentThread();
System.out.println(t+"正在执行A方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t+"运行A方法完毕");
}
public synchronized void mathodB(){
Thread t=Thread.currentThread();
System.out.println(t+"正在执行B方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t+"运行B方法完毕");
}
}
因为锁对象都是this,所以就算是不同的方法,也会是有互斥的现象发生。
说完了多线程同步,我们再来说说线程池。
线程池是在JDK1.5之后才有的。是为了方便管理线程控制线程数量和重用线程。
创建线程池一共有四种方式,这里只介绍一种(最常用的)。
mport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
* 解决两个问题:
* 1:控制线程数量
* 2:重用线程
* 当开发中发现需要创建大量的线程,或者线程频繁创建销毁时
* 就应该使用 线程池来管理线程
* @author Analyze
*
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
//固定大小线程池
ExecutorService threadpool=Executors.newFixedThreadPool(2);
//指派5个任务
for(int i=0;i<5;i++){
Runnable runn =new Runnable(){
public void run() {
Thread t=Thread.currentThread();
System.out.println(t+"正在执行任务。。。");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(t+"执行完毕!");
}
};
System.out.println("执行新任务");
threadpool.execute(runn);
}
//停止线程池
System.out.println("停止线程池");
threadpool.shutdown();//做完任务后停止,还有一种是shutdownNow(),立即停止,不管任务完成没完成
}
}