学习多线程之前,我们先要了解几个关于多线程的概念。
进程
线程
简而言之,一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
例如打开你的计算机上的任务管理器,会显示出当前机器的所有进程,QQ,Chrome等,当QQ运行时,就有很多子任务在同时运行。比如,当你边打字发送表情,边好友视频时这些不同的功能都可以同时运行,其中每一项任务都可以理解成“线程”在工作。
多线程
提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。
串行
并行
了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,能查杀病毒、清理垃圾、电脑加速等众多功能。
按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。
如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。
实现步骤
public class ThreadDemo1 {
public static void main(String[] args) {
//创建两个线程
ThreadDemo td = new ThreadDemo("zhangsan");
ThreadDemo tt = new ThreadDemo("lisi");
//执行多线程特有方法,如果使用td.run();也会执行,但会以单线程方式执行。
td.start();
tt.start();
//主线程
for (int i = 0; i < 5; i++) {
System.out.println("main" + ":run" + i);
}
}
}
//继承Thread类
class ThreadDemo extends Thread{
//设置线程名称
ThreadDemo(String name){
super(name);
}
//重写run方法。
public void run(){
for(int i = 0; i < 5; i++){
System.out.println(this.getName() + ":run" + i);
//currentThread() 获取当前线程对象(静态)。
//getName() 获取线程名称。
}
}
}
接口应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run 的无参方法。
实现步骤
public class RunnableDemo {
public static void main(String[] args) {
RunTest rt = new RunTest();
//建立线程对象
Thread t1 = new Thread(rt);
Thread t2 = new Thread(rt);
//开启线程并调用run方法。
t1.start();
t2.start();
}
}
//定义类实现Runnable接口
class RunTest implements Runnable{
private int tick = 10;
//覆盖Runnable接口中的run方法,并将线程要运行的代码放在该run方法中。
public void run(){
while (true) {
if(tick > 0){
System.out.println(Thread.currentThread().getName() + "..." + tick--);
}
}
}
}
实现步骤
public class CallableFutrueTest {
public static void main(String[] args) {
//创建对象
CallableTest ct = new CallableTest();
//使用FutureTask包装CallableTest对象
FutureTask<Integer> ft = new FutureTask<Integer>(ct);
for(int i = 0; i < 100; i++){
//输出主线程
System.out.println(Thread.currentThread().getName() + "主线程的i为:" + i);
//当主线程执行第30次之后开启子线程
if(i == 30){
Thread td = new Thread(ft,"子线程");
td.start();
}
}
//获取并输出子线程call()方法的返回值
try {
System.out.println("子线程的返回值为" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableTest implements Callable<Integer>{
//复写call() 方法,call()方法具有返回值
public Integer call() throws Exception {
int i = 0;
for( ; i<100; i++){
System.out.println(Thread.currentThread().getName() + "的变量值为:" + i);
}
return i;
}
}
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口的子类的run方法中。
实现Callable:
建议使用实现接口的方式创建多线程。
线程睡眠的原因
线程睡眠的方法
public class SynTest {
public static void main(String[] args) {
new Thread(new CountDown(),"倒计时").start();
}
}
class CountDown implements Runnable{
int time = 10;
public void run() {
while (true) {
if(time>=0){
System.out.println(Thread.currentThread().getName() + ":" + time--);
try {
//睡眠时间为1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
每隔一秒则会打印一次,打印结果为:
倒计时:10
倒计时:9
倒计时:8
倒计时:7
倒计时:6
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1
倒计时:0
扩展
该方法和sleep()方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。
public class SynTest {
public static void main(String[] args) {
yieldDemo ms = new yieldDemo();
Thread t1 = new Thread(ms,"张三吃完还剩");
Thread t2 = new Thread(ms,"李四吃完还剩");
Thread t3 = new Thread(ms,"王五吃完还剩");
t1.start();
t2.start();
t3.start();
}
}
class yieldDemo implements Runnable{
int count = 20;
public void run() {
while (true) {
if(count>0){
System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
if(count % 2 == 0){
//线程让步
Thread.yield();
}
}
}
}
}
sleep和yield的区别
当B线程执行到了A线程的.join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
join可以用来临时加入线程执行。
public static void main(String[] args) throws InterruptedException {
yieldDemo ms = new yieldDemo();
Thread t1 = new Thread(ms,"张三吃完还剩");
Thread t2 = new Thread(ms,"李四吃完还剩");
Thread t3 = new Thread(ms,"王五吃完还剩");
t1.start();
t1.join();
t2.start();
t3.start();
System.out.println( "主线程");
}
原stop方法因有缺陷已经停用了,那么现在改如何停止线程?现在分享一种,就是让run方法结束。
开启多线程运行,运行的代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
public class StopThread {
public static void main(String[] args) {
int num = 0;
StopTh st = new StopTh();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
//设置主线程执行50次,执行结束之后停止线程
while (true) {
if(num++ == 50){
st.flagChange();
break;
}
System.out.println(Thread.currentThread().getName() + "..." + num);
}
}
}
class StopTh implements Runnable{
private boolean flag = true;
public void run() {
while(flag){
System.out.println(Thread.currentThread().getName() + "stop run" );
}
}
public void flagChange(){
flag = false;
}
}
特殊情况
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
Thread类中提供了优先级的三个常量,代码如下:
MAX_PRIORITY = 10
MIN_PRIORITY = 1
NORM_PRIORITY = 5
------------------------------------------------------
ThreadDemo td = new ThreadDemo();
Thread t1 = new Thread(td,"张三");
//设置优先级
t1.priority(9);
//设置完毕
t1.start();
为什么要进行线程同步?
不同步会发生的问题?
public class SynTest {
public static void main(String[] args) {
//定义三个线程
MySyn ms = new MySyn();
Thread t1 = new Thread(ms,"线程1输出:");
Thread t2 = new Thread(ms,"线程2输出:");
Thread t3 = new Thread(ms,"线程3输出:");
t1.start();
t2.start();
t3.start();
}
}
class MySyn implements Runnable{
//共执行10次线程
int tick = 10;
public void run() {
while(true){
if(tick>0){
try {
//执行中让线程睡眠10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + tick--);
}
}
}
}
输出结果用以下图片展示,可以看到我勾选的部分都发生了冲突数据:
同步方法1
public synchronized void run() {
同步方法2
public void run() {
while(true){
//同步代码块
synchronized (this) {
if(tick>0){
try {
//执行中让线程睡眠10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + tick--);
}
}
}
}
加同步之后的输出数据为:
线程1输出: 10
线程2输出: 9
线程2输出: 8
线程2输出: 7
线程2输出: 6
线程2输出: 5
线程2输出: 4
线程3输出: 3
线程3输出: 2
线程3输出: 1
追加问题:如果同步函数被静态修饰之后,使用的锁是什么?静态方法中不能定义this!
静态内存是:内存中没有本类对象,但是一定有该类对应的字节码文件对象。 类名.class 该对象类型是Class。
所以静态的同步方法使用的锁是该方法所在类的字节码文件对象。 类名.class。代码如下:
public static mySyn(String name){
synchronized (Xxx.class) {
Xxx.name = name;
}
}
同步的前提:
如何找问题?
进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。
public class DeadLock {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockTest(true));
Thread t2 = new Thread(new DeadLockTest(false));
t1.start();
t2.start();
}
}
class DeadLockTest implements Runnable{
private boolean flag;
static Object obj1 = new Object();
static Object obj2 = new Object();
public DeadLockTest(boolean flag) {
this.flag = flag;
}
public void run(){
if(flag){
synchronized(obj1){
System.out.println("if lock1");
synchronized (obj2) {
System.out.println("if lock2");
}
}
}else{
synchronized (obj2) {
System.out.println("else lock2");
synchronized (obj1) {
System.out.println("else lock1");
}
}
}
}
}
死锁形成的必要条件总结(都满足之后就会产生):