注:该文章只做简单记录
线程的状态
原文链接:
https://www.cnblogs.com/wangyichuan/p/5990821.html
实现线程(四种方式)
- 继承Thread类
public class ThreadTest{
//main 也为一个线程
public static void main(String[] args) {
//创建MyTHread对象(线程)
MyThread myThread = new MyThread();
//调用start()
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(); //线程想要做的事
}
}
- 实现Runable接口
public class ThreadTest{
//main 也为一个线程
public static void main(String[] args) {
//创建MyTHread对象(线程)
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
//调用start()
thread.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(); //线程想要做的事
}
}
- 实现Callable接口(JDK5.0后)
与使用Runable相比,Callable功能更加强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
public class CallableTest {
public static void main(String[] args) {
Test test = new Test();
FutureTask<String> task = new FutureTask<String>(test);
new Thread(task).start();
try {
//获取Callable中的call方法的返回值
Object object = task.get();
System.out.println(object);
}catch (Exception e){
e.printStackTrace();
}
}
}
class Test implements Callable<String>{
@Override
public String call() throws Exception {
return "...";
}
}
- 使用线程池(JDK5.0后,实际开发中常用...)
- 背景:经常创建和销毁,使用量特别大的资源,比如开发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
…
- 线程池相关API(内容较多)
- JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行
Runnable
<T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行
Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
* 面试题:创建多线程有几种方式?四种!
* @author shkstart
* @create 2019-02-15 下午 6:30
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
由于使用Executors创建线程池有相应的弊端
还可以使用以下方法
/**
* 测试ThreadPoolExecutor对线程的执行顺序
**/
public class ThreadPoolSerialTest {
public static void main(String[] args) {
//核心线程数
int corePoolSize = 3;
//最大线程数
int maximumPoolSize = 6;
//超过 corePoolSize 线程数量的线程最大空闲时间
long keepAliveTime = 2;
//以秒为时间单位
TimeUnit unit = TimeUnit.SECONDS;
//创建工作队列,用于存放提交的等待执行任务
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
ThreadPoolExecutor threadPoolExecutor = null;
try {
//创建线程池
threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new ThreadPoolExecutor.AbortPolicy());
//循环提交任务
for (int i = 0; i < 8; i++) {
//提交任务的索引
final int index = (i + 1);
threadPoolExecutor.submit(() -> {
//线程打印输出
System.out.println("大家好,我是线程:" + index);
try {
//模拟线程执行时间,10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
暂时记一下...
多线程安全问题
- 比如:经典的买票问题,假如有两个窗口买票,a窗口和b窗口,那么a窗口相当于一个线程,b窗口也是一个线程,而ticket相当于共享数据。
- 场景:当a线程操作ticket时,操作尚未完成,这个时候b线程参与进来,也操作车票,这时就发生了安全问题。
- 解决:当a线程在操作ticket时,其他线程不能参与进来,只能等到a线程此次操作完后,其他线程才开始操作ticket。
在java中,我们通过同步机制解决安全问题
synchronize(同步监视器){
//需要同步的代码
}
- 操作共享数据的代码,即为需要被同步的代码。
- 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
--要求:多个线程必须要共用同一把锁。
补充:
同步监视器必须是唯一的
在继承Thread创建多线程的方式中,我们可以考虑使用当前类充当同步监视器。
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
- 方式:
public class Security {
public static void main(String[] args) {
SecurityTest securityTest = new SecurityTest();
new Thread(securityTest).start();
new Thread(securityTest).start();
}
}
- 同步代码块
class SecurityTest implements Runnable {
//票 共享数据
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println("线程" + Thread.currentThread().getName() + "在买票第" + ticket + "张票!");
ticket--;
}
}
}
单例模式 -- 线程安全
public class Singleton {
private volatile static Singleton singleton; //volatile 刚开始没有加 出现问题
//
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 先说说为什么要双重锁的问题。在早期的jvm中,synchronized存在巨大的性能开销。如果getInstance的竞争很小,甚至没有竞争,那么synchronized就存在很大的冗余性能开销。所以通过双重检查机制避免不必要的锁操作。
(DCL本质上也就是减少了锁粒度)然而这样的单例还不是最安全的(没加volatile),代码执行到singleton不为null时,可能会出现singleton的引用还没有被完全初始化的情况。 - 这里涉及到指令重排序
- 这里有有些地方题主还没有深入......
死锁
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,就形成了现成的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
- 解决方法
- 专门的算法、原则 2. 尽量减少同步资源的定义 3. 尽量避免嵌套同步
(这个地方不在多讲)
Lock
JDK1.5之前,我们在多线程的环境下要想保证线程安全,就必须要使用synchronized关键字来实现对象锁或者类锁,以此满足这样的需求,JDK1.5之后则使用Lock来实现更加细粒度的锁。
//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);
public class Security {
public static void main(String[] args) {
SecurityTest securityTest = new SecurityTest();
new Thread(securityTest).start();
new Thread(securityTest).start();
new Thread(securityTest).start();
}
}
class SecurityTest implements Runnable {
//票 共享数据
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println("线程" + Thread.currentThread().getName() + "在买票第" + ticket + "张票!");
ticket--;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
- lock是显示锁(手动开启和关闭锁),synchronize是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronize有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)
- Lock --> 同步代码块(已经进入了方法体,分配了相应资源) --> 同步方法(在方法体外)
线程通信
线程通信涉及到的三个方法 以下方法是定义在java.lang.Object中
- wait() 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify() 一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤优先级最高的那个。
- notify() 一旦执行此方法,就会唤醒所有被wait的一个线程
以上方法必须使用在同步代码块或同步方法中(以上方法其实是同步监视器调用的)
时间:2019-12-5 23:50:44