1、线程的理解:
(1)线程的使用:
线程的学习难点并不在于如何创建、开启一个线程,而是应该何时创建,线程如何同步,使得程序能够运行得到预期的结果。
比如文字处理程序 你可以设置每隔多长时间 输入的内容可以自动的保存 而这个保存操作并不需要你的介入 而是使用一个线程专门负责这个操作,在这个程序中至少得有两个线程 一个便是主线程 用来捕获你从键盘输入的内容 而另一个线程则负责定时保存输入的内容。
在后面的socket的学习中也用到了多线程:
(2)线程的执行:
只有在多处理器的机器上, 多线程才会被真正的同时运行,在单处理机上多线程是占用分配的时间片来运行,因为处理机的速度比较快 使得我们看起来它像是在同时运行,也就是我们操作系统中所说的并发和并行的概念。
2、创建线程:
方法一:定义线程实现Runnable接口
public class MyThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++) {
System.out.println("myThread"+i);
}
}
}
方法二:定义一个Thread的子类并重写run方法
缺点:Java中OOP单继承的局限性
public class MyThread1 extends Thread {
public void run() {
for(int i=0;i<10;i++) {
System.out.println("MyThread"+i);
}
}
}
这里需要注意的是 Thread类这里用到了代理设计模式。线程体(也就是我们要执行的具体任务)实现了Runable接口和run方法。同时Thread类也实现了Runnable接口。此时线程体就相当于目标角色,Thread就相当于代理角色。当程序调用了Thread的start方法后,thread的run()方法会在某个特定的时候被调用。
方法三:实现Callable接口
class Mythread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"启动了");
return 1024;
}
}
方法四:使用线程池
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool=new ThreadPoolExecutor(2,5,1L, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
try{
threadPool.execute(new MyThread());
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
测试:
public class MainTread {
public static void main(String[] args) {
Runnable r1=new MyThread();
//r1.run();这样只是执行了线程体,而不是开辟出类一个线程与主线程同时运行
//这样就开辟出来一个新的线程 Thread(Runnable target)
Thread t=new Thread(r1);//要启动一个新的线程就必须new一个Thread对象出来
t.start();//线程启动调用start方法 线程执行默认运行的run()方法
/*
方法二:
MyThread1 mt=new MyThread1();
mt.start();
*/
//方法三
FutureTask<Integer> futureTask=new FutureTask<>(new Mythread2());
Thread t1=new Thread(futureTask);
t1.start();
int result01=100;
int result02=futureTask.get();//建议放到最后,要求获得Callable线程的计算结果,如果没有计算完成,就去强求,会导致阻塞直到计算完成
System.out.println("****result*****"+(result01+result02));
}
}
在java编程思想中提到Callabel是Java SE5引入的一种具有类型参数的泛型,它的类型表示的是方法call()(而不是run())中返回的值,并且必须使用ExecutorService.submit()方法调用它。
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id=id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult "+id;
}
}
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService exec=new ThreadPoolExecutor(2,5,1L, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
ArrayList<Future<String>> results=new ArrayList<>();
for(int i=0;i<10;i++){
results.add(exec.submit(new TaskWithResult(i)));
}
for(Future<String> fs:results)
try{
System.out.println(fs.get());
}catch(InterruptedException e){
System.out.println(e);
return;
}catch (Exception e){
e.printStackTrace();
}finally {
exec.shutdown();
}
}
}
上面的代码源自Java编程思想,当我敲完这段代码的时候,竟然在质疑它执行exec.shutdown的位置是否不对,从上面可以看出每次for循环都执行了一次exec.shutdown(),并且并不会报错,说明执行shutdown方法并不是将线程池给关闭了。关于shutdown()方法这里给出几个简短的总结:
(1)执行shutdown方法后,线程池不在接收新的任务,继续提交任务会根据线程池的拒绝策略给你出响应(如抛出异常);
(2)等待队列中的任务仍然会继续执行;
(3)正在执行中的任务并不会立即被中断执行,这个需要在run方法中单独处理interrupted状态,interrupt更类似一个标志位,不会直接打断线程的执行。
如何优雅的关闭线程池,可以参考以下文章,后期自己也会对这些做出总结。
线程池的优雅关闭实践
关闭线程池的正确姿势,shutdown(), shutdownNow()和awaitTermination() 该怎么用?
3、线程的管理
(1)join()
等待该线程终止,在当前线程中调用另一个线程的jion方法,则当前线程转入阻塞状态,直到另一个线程运行结束后,当前线程才会由阻塞状态转为就绪状态
我们通过实现下面这道题来理解join()方法
编写一个程序,开启 3 个线程,这三个线程的分别为 A、B、C,每个线程打印对应的“A”、“B”、“C” 10 遍,要求输出的结果必须按顺序显示。如:ABCABCABC……
public class Test {
public static void main(String[] args) {
try {
for (int i=0;i<10;i++){
Thread t1=new Thread(new Task("A"));
Thread t2=new Thread(new Task("B"));
Thread t3=new Thread(new Task("C"));
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("Main Thread");
/*
output
ABCABCABCABCABCABCABCABCABCABC
Main Thread
* */
}
static class Task implements Runnable{
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.print(this.name);
}
}
}
此处代码运行的结果是 待tr tr1线程运行结束后 主线程才会开始运行
(2)yield和sleep
yield和sleep(0)实现的效果一样,因此我们可以发现它俩的区别是sleep可以规定该线程多长时间内不可以抢占处理机,而yield()不可以,它意味着该线程放弃处理机后立马可以参加抢占处理机。
代码实现:
public class MyThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<50;i++) {
/*
* 每循环一次 该线程让出了处理机 即处理机重新选择分配给一个线程 进行运行 仍有可能分配给原来的线程
* */
Thread.yield();
System.out.println("MyThread"+i);
}
}
}
public class MyThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);//该线程休眠 5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<50;i++) {
System.out.println("MyThread"+i);
}
}
}
(3)设置线程的优先级
需要注意的是Java中优先级高的线程只能有机会优先被执行 而不会绝对被优先执行
public class MainThread {
/*
* 线程优先级设置测试
* */
public static void main(String[] args) {
MyThread1 mt1=new MyThread1();
MyThread2 mt2=new MyThread2();
Thread tr1=new Thread(mt1);
Thread tr2=new Thread(mt2);
int p1 = tr1.getPriority();
int p2 = tr2.getPriority();
System.out.println(p1+" "+p2);//线程的默认优先级为5
//设置线程的优先级
tr1.setPriority(10);
tr2.setPriority(7);
tr1.start();
tr2.start();
for(int i=0;i<50;i++) {
System.out.println("MainThread "+i);
}
}
}
线程同步
(1)wait(),notify()与 synchronized
需要先明白以下几个问题:
1、如何使用?
(1) obj.wait()(等待) 与 obj.notify()(唤醒) 必须要与synchronized(obj)方法一起使用
(2) wait()必须在synchronized代码块中,它使得当前线程等待,直到其它线程调用此对象的notify()或notifyAll()方法,即其它线程唤醒自己。
(3) notify()方法可不可以自己唤醒自己? 答案当然是NO,notify()唤醒的是在等待队列中的线程,自己就没有在等待队列,需要注意的是notify()唤醒的是等待队列中的随机一个线程, notifyAll()可将等待队列中的所有的线程全部唤醒
2、wait()与sleep的区别 。
sleep是线程的方法 wait是object的方法 sleep释放的CPU的抢占权 wait()将CPU的抢占权跟锁全部都释放
3、为什么要使用 synchronized?
多线程操作同一个对象的时候要加锁,不加锁的话会导致程序的执行无可再现性,每次的运行结果都不相同。
下面我们实现一个简单的生产者消费者来理解线程同步的这些方法。
简单的生产者消费者模式(参考自参考资料1)
/*
使用wait()和notify()方法实现
缓冲区满或者为空时都调用wait()方法等待。
当生产者生产了一个产品或者消费者消费了一个产品之后会唤醒所有线程。
*/
public class Test1 {
private static Integer count=0;
private static final Integer FULL=10;
private static String LOCK="lock";
public static void main(String[] args) {
Test1 test1=new Test1();
for (int i=0;i<5;i++){
new Thread(test1.new Producer()).start();
}
for (int i=0;i<5;i++){
new Thread(test1.new Consumer()).start();
}
}
class Producer implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK){
while(count==FULL){
try{
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName()+"生产者生产,目前总共有"+count);
LOCK.notifyAll();
}
}
}
}
class Consumer implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK){
while (count==0){
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"消费者消费,总共有"+count);
count--;
LOCK.notifyAll();
}
}
}
}
}