public class Threads {
volatile int num;
int num2;
@Test
void test() throws InterruptedException {
/*开启一个新线程最简单的方法,继承Thread类,重写run方法*/
new Thread(){
@Override
public void run() {
System.out.println("New Thread");
}
/*调用start(),启动线程*/
}.start();
/*实现Runnable接口*/
Thread t2 = new Thread(() -> System.out.println("New Runnable"));
t2.start();
/*实现Callable接口
* 由于Callable接口的call方法是有返回值的
* 这里可以使用FutureTask来接收返回值*/
FutureTask<String> ft = new FutureTask(() -> "New Callable");
Thread t3 = new Thread(ft);
t3.start();
try {
/*当调用FutureTask的get方法时,当前线程会进入等待状态
* 直到获取到get方法的返回值后才继续运行*/
System.out.println(ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
/*线程安全*/
/*当一个变量被多个线程访问时,即变量被线程1做修改操作时,线程2又访问了进来,线程1完成了操作变量值已被修改
* 但线程2访问到的仍是未被修改前的值,这样就出现了线程不同步问题
* 解决方案 共享变量使用volatile关键字修饰,他可以保证当变量发生改变时对所有线程都是立即可见的
* 但他虽然能保证变量的原子性,但会阻止编译器对代码的优化而降低效率,所以一般使用synchronized来代替volatile
* 如下示例,两个线程一起打印0-99的整数*/
new Thread(() -> {
while (num<100) {
System.out.println(num++);
}
}).start();
new Thread(() -> {
while (num<100){
System.out.println("----"+num++);
}
}).start();
/*方案2:使用synchronized关键字*/
final Object o = new Object();
new Thread(() -> {
while (num2<100){
/*使用时要传递一个引用数据类型实例来当做“锁”,当该锁被X线程拿到时,其他线程会进入等待状态,
* 只有X线程执行完锁中的代码时才会释放锁,此时其他线程及X线程才会再次争夺锁*/
synchronized (o){
System.out.println(num2++);
}
}
}).start();
new Thread(() -> {
while (num2<100){
synchronized (o){
try {
/*使用sleep方法使当前线程进入睡眠状态,参数单位:毫秒*/
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println((num2++)+"-----------------");
}
}
}).start();
/*同样,可以用synchronized来修饰一个方法,当此方法被X线程访问时,其他线程要想访问此方法就必须等待X线程的访问结束
* 应用实例*/
class AThread{
//在线程中不允许非final修饰的局部基本类型变量出现
int count = 0;
int check = 0;
}
AThread a = new AThread();
new Thread(() -> {
while (true){
synchronized (o){
/*当count已经不低于10的时候此时的线程已经拿到了锁,此时应该结束循环,
但仍会将锁中的逻辑再次执行一遍。所以这个控制循环结束的条件
* 应在拿到锁之后判断*/
if(a.count>=10){
return;
}
while(a.check%3==0){
System.out.println("a");
a.check++;
}
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (o){
if(a.count>=10){
return;
}
while (a.check%3==1){
System.out.println("b");
a.check++;
}
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (o){
if(a.count>=10){
return;
}
while(a.check%3==2){
System.out.println("c");
a.check++;
a.count++;
}
}
}
}).start();
/*wait(),notify(),notifyAll()
* wait():当前线程调用此方法会立即将锁释放,并且使当前线程进入等待状态,只有当被其他线程唤醒
* 时才会进入就绪队列。否则,此线程将被一直挂起
* notify():当某个线程调用此方法时会从等待队列中取出一个线程
* notifyAll():类似于notify(),会释放等待队列中的所有线程
* 这些方法要声明在synchronized中,因为操作的是锁,若当前线程未持有锁则会抛出相关异常*/
/*应用实例:两个线程交替打印0-100的整数*/
a.count = 0;
Thread thread = new Thread(() -> {
while (true){
synchronized (o){
if (a.count>100){
return;
}
/*current():获得当前线程的实例
getName():获得当前线程名称,如果未被设置则使用默认值
设置X线程名称,X.setName(String name)*/
System.out.println(Thread.currentThread().getName()+":"+a.count++);
//*取出一个被挂起的线程,此操作不会释放锁,只是让被取出的线程进入就绪状态*//*
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
new Thread(thread).start();
new Thread(thread).start();
/*JDK 5中提供的"锁"*/
/*可以使用默认的无参构造器构造一个Lock实例,可以传递一个公平性参数(true or false)
* 此参数设置的公平性是指:当一个线程本次使用锁完成执行后,该线程下次便不再参与锁的争夺*/
Lock lock = new ReentrantLock(/*公平性参数默认为false*/);
/*此时这个锁就是一个“公平锁”,利用这一特性同样可以完成以上的交替打印操作*/
final Lock lock1 = new ReentrantLock(true);
/*使用示例*/
a.count = 0;
Thread thread1 = new Thread(() -> {
while (true){
/*上锁*/
lock1.lock();
if(a.count>100){
return;
}
System.out.println(Thread.currentThread().getName()+":"+a.count++);
/*解锁*/
lock1.unlock();
}
});
new Thread(thread1).start();
new Thread(thread1).start();
/*另外,在提一点,在多线程同步问题中,一定要确保各个线程拿到和操作的锁是 同一个
* 将释放锁的操作放在try...finally{}中,即便是当前线程出现异常也不影响其他线程的执行*/
/*事实上从Hello World开始我们就已经在用多线程了,除了默认的main
* 还一直存在一个垃圾回收线程 gc*/
/*使用线程池来构建线程,阿里开发手册中严格禁止使用Executors来创建线程池*/
// ExecutorService tpe = Executors.newFixedThreadPool(3);
/*所以最好手动创建线程池,自定义拒绝策略,有效避免OOM
* 使用ThreadPoolExecutor,该类中重载了许多构造器,但最终都会使用此构造器来完成构建
* public ThreadPoolExecutor(int corePoolSize,线程池核心数量,其中的线程任务一直保留
int maximumPoolSize,最大线程数,当所有的核心线程均被占用时,线程池会开启新的线程
long keepAliveTime,线程存活时间,当线程任务结束后在规定时间内没有再次开启任务则自动关闭
TimeUnit unit,用于设置存活时间的单位
BlockingQueue<Runnable> workQueue,当所有线程均被占用时,新进来的任务会进入等待队列,当有线程执行完毕后
会从队列中取出任务继续执行
ThreadFactory threadFactory,作用是产生线程,可以自定义线程的类型
RejectedExecutionHandler handler 拒绝策略,设置当线程和队列满的时候对于新进来任务的处理方式
)
线程队列:
ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
DelayQueue: 使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 不存储元素的阻塞队列。
LinkedTransferQueue: 由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 由链表结构组成的双向阻塞队列
拒绝策略:
AbortPolicy 丢弃任务,抛出异常
DiscardPolicy 使用execute方法执行任务
DiscardOldestPolicy 抛弃线程池最前方的任务,并重新尝试执行新任务
CallerRunsPolicy 直接丢弃任务
当JDK提供的拒绝策略不满足需求时可以自定义拒绝策略,实现RejectedExecutionHandler接口并重写rejectedExecution方法
将逻辑写在此方法中即可
*
*
*
* */
ThreadPoolExecutor tp = new ThreadPoolExecutor(10,20,3,TimeUnit.SECONDS,
new LinkedBlockingQueue(), new ThreadPoolExecutor.CallerRunsPolicy());
/*使用submit方法向线程池提交一个任务*/
tp.submit(() -> System.out.println("New ThreadPool"));
/*如何测试多线程场景下的程序执行时间*/
long begin = System.currentTimeMillis();
a.count = 0;
tp.submit(() -> {
try {
Thread.sleep(999);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.count++;
});
/*这样不行,因为线程任务交给了线程池执行,main线程只进行了提交操作
* 所以此处计算的时间并没有将线程任务执行时间包括在内*/
System.out.println(System.currentTimeMillis() - begin);
/*应该等待其他线程全部执行完毕后一起统计时间
* 具体的实现:可以预先定义一个flag变量(如上面的a.count),当一个线程执行完毕之后该变量自增一
* 当其值等于当前程序的线程数量时便打印执行时间*/
/*具体实现*/
while (a.count!=1){
}
System.out.println(System.currentTimeMillis() - begin);
/*或者可以使用上文中FutureTask+Callable的方式
get()方法获取到返回值前,调用线程会一直处于阻塞状态
同样可以实现测试运行时间的操作*/
}
牛哔