多线程的话,其实只要学了点儿JAVA的应该都是知道创建的三种方式,之前也是没有仔细对比和思考过,为啥是这样,然后经过一次面试之后,就发现自己在多线程这方面太弱了,所以就来补一补:
梳理一下自己多线程的知识盲区和线程池相关的,主要是自己能理解的更清楚些,所以内容全部手敲,代码也全部手敲,eclipse下实现
创建线程的三种方式
1.继承Thread类重写run方法
package crazyJava;
/*
* 通过继承Thread类重写run方法来实现的多线程
*/
public class FirstThread extends Thread {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new FirstThread().start();
new FirstThread().start();
}
}
}
}
2.实现Runnable接口写run方法
package crazyJava;
//通过实现runnable接口来实现的多线程
public class SecondThread implements Runnable{
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
SecondThread st=new SecondThread();
new Thread(st,"新线程1").start();
new Thread(st,"新线程2").start();
}
}
}
}
3.实现Callable接口写call方法,交由FutureTask对象作为Target
package crazyJava;
import java.util.concurrent.*;
public class ThirdThread {
public static void main(String[] args) {
// TODO 自动生成的方法存根
ThirdThread rt=new ThirdThread();
FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+" 的循环变量i的值:"+i);
}
return i;
});
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值:"+i);
if(i==20) {
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:"+task.get());
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
上面是通过Lambda表达式来实现的Callable对象
package crazyJava;
import java.util.concurrent.*;
public class ThirdThread2 implements Callable{
private int i;
@Override
public Object call() throws Exception {
// TODO 自动生成的方法存根
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的内循环变量i: "+i);
}
return i;
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
ThirdThread2 rt=new ThirdThread2();
FutureTask<Integer> task=new FutureTask<Integer>(rt);
//ThirdThread2 rt2=new ThirdThread2();
FutureTask<Integer> task2=new FutureTask<Integer>(rt);
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的内循环变量i: "+i);
if(i==20) {
new Thread(task,"带有返回值的线程1").start();
new Thread(task2,"带有返回值的线程2").start();
}
}
try {
System.out.println("两个线程的返回结果的和"+(task.get()+task2.get()));
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
线程同步通信
这里其实还是主要在于理解,因为涉及到通信以及同步,也就是一定要共享资源了,所以必须要进行线程同步实现安全访问共享资源,插一句,也就是为什么和ThreadLocal局部变量的性质完全不同的所在,因为二者解决的本质问题就不同,所以不要混淆。因为锁的原因,这些线程并不是并行在跑的,想要并行的化,也可以理解下ForkJoinPool,一个挺厉害的线程池。
一个习题,写两个线程,其中一个线程打印1-52,另一个线程打印A-Z,打印顺序应该是12A34B...5152Z。如下图所示结果:
这个题可以很好的练习线程的创建、通信、同步。三种基本的创建方式+两种常用的线程通信方式。当然线程的通信方式有很多其他的方式比如使用volatile、使用JUC包里的atom包里的atomInteger、使用BlockingQueue等等。这里主要用synchronized+flag+flag.wait+flag.notifyAll和lock+condition+awit+signalAll来实现。
1.继承Thread类实现线程+Synchronized通信
package crazyJava;
//打印数字的线程
class thread1 extends Thread{
private comflag flag;
public thread1(String name,comflag flag) {
super(name);
this.setFlag(flag);
}
public void run() {
for (int i = 0; i < 52; i += 2) {
synchronized (flag) {
try {
while (flag.flag != 0)
flag.wait();
System.out.print((i + 1) + "" + (i + 2));
flag.flag = 1;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
//打印字母的线程
class thread2 extends Thread{
private comflag flag;
public thread2(String name,comflag flag) {
super(name);
this.setFlag(flag);
}
public void run() {
for (int i = 0; i < 26; i++) {
synchronized (flag) {
try {
while (flag.flag != 1)
flag.wait();
char temp = (char) ('A' + i);
System.out.print(temp);
flag.flag = 0;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
class comflag{
int flag=0;
}
public class Xiti_16_01 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
comflag com=new comflag();
thread1 th1=new thread1("甲",com);
thread2 th2=new thread2("乙",com);
th1.start();
th2.start();
}
}
2.Runnable接口实现多线程+synchronized
package crazyJava;
class myrun1 implements Runnable{
private comflag flag;
public myrun1(comflag flag){
this.setFlag(flag);
}
@Override
public void run() {
// TODO 自动生成的方法存根
for (int i = 0; i < 52; i += 2) {
synchronized (flag) {
try {
while (flag.flag != 0)
flag.wait();
System.out.print((i + 1) + "" + (i + 2));
flag.flag = 1;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
class myrun2 implements Runnable{
private comflag flag;
public myrun2(comflag flag){
this.setFlag(flag);
}
@Override
public void run() {
// TODO 自动生成的方法存根
for (int i = 0; i < 26; i++) {
synchronized (flag) {
try {
while (flag.flag != 1)
flag.wait();
char temp = (char) ('A' + i);
System.out.print(temp);
flag.flag = 0;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
public class Xiti_16_011 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
comflag flag=new comflag();
new Thread(new myrun1(flag)).start();
new Thread(new myrun2(flag)).start();
}
}
3.Callable+FutureTask+synchronized
不难发现的是,越来越复杂了,其实也是越来越有用,因为FutureTask对象是带返回值的,因此我们可以通过task对象来调用get()方法拿到返回值,而参数的化也是可以我们来指定或者在结果的时候强制转换一下。这也是当时为啥可以用该方法来分别计算多个任务再求和,我们可以用在主线程里用task.get()获取返回值求和,只是这个时候当子线程的任务没有完成的时候,主线程因为要获取返回值是会被阻塞挂起来的。
package crazyJava;
import java.util.concurrent.*;
class mycal1 implements Callable<Object>{
private comflag flag;
public mycal1(comflag flag) {
this.setFlag(flag);
}
@Override
public Object call() throws Exception {
// TODO 自动生成的方法存根
for (int i = 0; i < 52; i += 2) {
synchronized (flag) {
try {
while (flag.flag != 0)
flag.wait();
System.out.print((i + 1) + "" + (i + 2));
flag.flag = 1;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
return null;
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
class mycal2 implements Callable<Object>{
private comflag flag;
public mycal2(comflag flag) {
this.setFlag(flag);
}
@Override
public Object call() throws Exception {
// TODO 自动生成的方法存根
for (int i = 0; i < 26; i++) {
synchronized (flag) {
try {
while (flag.flag != 1)
flag.wait();
char temp = (char) ('A' + i);
System.out.print(temp);
flag.flag = 0;
flag.notifyAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
return null;
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
public class Xiti_16_012 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
comflag flag=new comflag();
FutureTask<Object> task=new FutureTask<>(new mycal1(flag));
FutureTask<Object> task2=new FutureTask<>(new mycal2(flag));
new Thread(task,"甲").start();
new Thread(task2,"乙").start();
}
}
4.继承Thread类+Lock+Condition+flag
package crazyJava;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
class thread_c1 extends Thread{
private comflag_c flag;
public thread_c1(comflag_c flag) {
this.flag=flag;
}
public void run() {
for (int i = 0; i < 52; i += 2) {
flag.lock.lock();
try {
while (flag.flag!= 0)
flag.con.await();
System.out.print((i + 1) + "" + (i + 2));
flag.flag = 1;
flag.con.signalAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}finally {
flag.lock.unlock();
}
}
}
}
class thread_c2 extends Thread{
private comflag_c flag;
public thread_c2(comflag_c flag) {
this.flag=flag;
}
public void run() {
for (int i = 0; i < 26; i++) {
flag.lock.lock();
try {
while (flag.flag != 1)
flag.con.await();
char temp = (char) ('A' + i);
System.out.print(temp);
flag.flag = 0;
flag.con.signalAll();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}finally {
flag.lock.unlock();
}
}
}
}
class comflag_c{
Lock lock=new ReentrantLock();
Condition con=lock.newCondition();
int flag=0;
}
public class Xiti_16_01c1 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
comflag_c flag=new comflag_c();
new thread_c1(flag).start();
new thread_c2(flag).start();
}
}
5.BlockingQueue+flag
package crazyJava;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer1 extends Thread{
private BlockingQueue<Integer> bq;
private comflag flag;
public Producer1(BlockingQueue<Integer> bq,comflag flag) {
this.bq=bq;
this.setFlag(flag);
}
public void run() {
for (int i = 0; i < 52; i += 2) {
produce(i);
}
}
@SuppressWarnings("finally")
public int produce(int i) {
try {
bq.put(i);
while(flag.flag!=0) {System.out.print('-');};//如果旗标指示现在应该consumer线程执行,则当前
//循环等待,知道阻塞挂起当前线程
System.out.print((i + 1) + "" + (i + 2));
flag.flag=1;
} catch(InterruptedException e) {
System.out.println(e);
}finally {
return 1;
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
class Consumer1 extends Thread{
private BlockingQueue<Integer> bq;
private comflag flag;
public Consumer1(BlockingQueue<Integer> bq,comflag flag) {
this.bq=bq;
this.setFlag(flag);
}
public void run() {
for (int i = 0; i < 26; i++) {
consume(i);
}
}
public void consume(int i) {
try {
bq.take();//这里如果不可取是会阻塞当前线程,但是一旦可取且取了之后,生产者线程也唤醒了
//因此如果不加下面的while循环判断旗标,则有可能会出现错乱的执行,因为生产者可以抢占CPU,同理
//生产者那里也需要一个循环判断旗标的过程,果然还是得自己亲自手敲一遍,尽信书,则不如无书
while(flag.flag!=1) {System.out.print('-');}
char temp = (char) ('A' + i);
System.out.print(temp);
flag.flag=0;
} catch(InterruptedException e) {
System.out.println(e);
}
}
public comflag getFlag() {
return flag;
}
public void setFlag(comflag flag) {
this.flag = flag;
}
}
public class Xiti_16_01_BQ {
public static void main(String[] args) {
// TODO 自动生成的方法存根
comflag flag=new comflag();
BlockingQueue<Integer> bq=new ArrayBlockingQueue<>(1);
new Producer1(bq,flag).start();
new Consumer1(bq,flag).start();
}
}
我真的有点儿服了现在写书的人,感觉并不完全靠谱,所以这个图也让我更明白了旗标flag的作用,以及如何使用旗标,也更加理解了所谓的锁,锁类以及阻塞队列的作用,其实大家的原理都差不多,只是具体实现和用法不一样。
多线程同步通信总结
在用synchronized对象锁的时候,我们做线程的同步通信的时候,两个线程共享了一个flag对象,而对象的flag.wait()方法阻塞当前线程,并释放当前线程持有的对象锁,因此可以实现线程的切换;falg.notify()和notifyAll()唤醒被flag对象锁阻塞的线程们,区别在与flag.notify()唤醒一个,而flag.notifyAll()唤醒所有。在使用Lock锁类的时候,原理一样的,flag实现线程切换或者说通信,只是这个时候用的锁对象来实现锁,用condition对象来实现线程的阻塞和唤醒。在用BlockingQueue的时候,也是一样的,flag实现切换,不同的是,BlockingQueue是一个同步工具的队列,里面有一个生产者和消费者线程,put和take来实现的放入和取出,是会阻塞线程的。上面的代码也具体的反应了这段话,可以理解一些了。