为什么有线程安全问题?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
线程安全解决办法:
问:如何解决多线程之间线程安全问题?
答:使用多线程之间同步synchronized或使用锁(lock)。(锁在代码块执行完毕或者抛出异常之后就会释放)
问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
问:什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
同步代码块
什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
就是同步代码块
synchronized(对象)//这个对象可以为任意对象
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
private static Object oj = new Object();
public void sale() {
// 前提 多线程进行使用、多个线程只能拿到一把锁。
// 保证只能让一个线程 在执行 缺点效率降低
synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
同步函数
什么是同步函数?
答:在方法上修饰synchronized 称为同步函数
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
静态同步函数
答:什么是静态同步函数?
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示
synchronized (ThreadTrain.class) {
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
总结:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件。
java多线程管家——Executor
Executor允许你管理异步任务的执行,而无须显示地管理线程的生命周期。ExecutorService知道如何构建恰当的上下文来执行Runnable对象。
1.创建ExecutorService
通过Executors能够创建两种方式的ExectorService。第一种、CachedThreadPool会为每个传入的任务新创建一个线程
ExecutorService exec = Executors.newCachedThreadPool();
第二种、FixedThreadPool可以一次性预先执行代价高昂的线程分配,所以可以用来限制线程的数量。这可以节省时间,因为你不必为每个任务都固定的付出创建线程的开销。
ExecutorService exeService = Executors.newFixedThreadPool(5);
2.把任务附着给ExecutorService
有了executor,你只需要定义任务并将任务对象传递给executor即可。
exeService.execute(new Task());
3.让所有任务开始执行
这两个方法会让之前提交到该exectuor的所有任务开始执行。为了避免启动后,会被注入新的任务,必须在你将所有线程注入后,执行关闭操作以保证这一点。
exeService.shutdown();
总结:
|——原来:想执行任务
| | |——1.定义任务
| | |——2.创建任务对象交由Thread对象操纵
| | |——3.显示的调用Thread对象的start()方法
| |——遇到问题:比较繁琐,总得自己启动线程调用;本质上是由main函数调用的
|——现在:使用java.util.concurrent.Executor(执行器)来管理Thread对象
| | |——1.ExecutorService exec = Executors.newAcahedThreadPool();
| | |——2.exec.execute(new Task());
| | |——3.exec.shutdown();
| |——在客户端(显示调用线程执行)和执行任务之间提供了一个间接层,用以执行任务;撇开了main函数,由executor直接进行了调用
| |——允许你管理异步任务的执行,而无须显示地管理线程的生命周期
三、其他
1.何时使用哪种线程池呢?
CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选
只有当这种方式会引起问题时,才需要切换到FixedThreadPool。
2.SingleThreadExecutor
SingleThreadExecutor就像是线程数量为1的FixedThreadPool
|——在另一个线程中连续运行的任何事务来说都很有用(重点是连续运行,因为这样可以顺序接受处理),故SingleThreadPool会序列化所有提交给它的任务, 并会维护它自己隐藏的悬挂任务队列
|——例如:向SingleThreadExecutor提交多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有任务使用相同线程。
3.自定义线程工厂
每个静态的ExecutorService创建方法都被重载为接受一个ThreadFactory对象,该对象将被用来创建新的线程。例如:
public class TaskDaemonFactory implements ThreadFactory{
public Thread newThread(Runnable r){
Thread t = new Thread(r);return t;
}
}
想使用自己定义的线程工厂
ExecutorService exec = Executors.newCachedThreadPool(new TaskDaemonFactory());
这样可以通过具体的要求来改造线程。