1.几个概念
1.程序:指唯一完成某个任务,用某种编程语言编写的一组指令的集合
2.进程:指程序的一次执行过程,例如我们启动了qq,word等,同时可以打开多个qq,就是说一个程序可以有多个进程
3.线程:进程的进一步细分,是一个程序内部执行的一条路径,一个进程可以有多个线程,一个线程就是一个指令,cpu调度的最小单位,由cpu一条一条的执行。一个进程同时执行多个线程,那么就是多线程。
4.并行:多个cpu同时执行多个任务
5.并发:一个cpu同时执行多个任务
2.多线程的创建
1.继承Thread类
1.步骤
1.让某个类成为Thread类的子类
2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
3.创建Thread类的对象后,调用start()方法,启动线程
class 线程类 extends Thread{
public void run(){
线程的任务
}
}
public static void main(String[] args){
线程类 线程类对象 = new 线程类();
线程类对象.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Windows extends Thread { private int ticket=100; @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"正在卖第"+(101-ticket)); ticket--; } } public static void main(String[] args) { Windows w = new Windows(); Thread t1 = new Thread(w,"窗口1"); Thread t2 = new Thread(w,"窗口2"); Thread t3 = new Thread(w,"窗口3"); t1.start(); t2.start(); t3.start(); } }
2.注意:
如果我们要通过继承 Thread 类去实现多线程,那么我们就需要重写 Thread 类中的 run方法,然后去构建继承了 Tihread 类的对象,最后通过调用 start 方法去开启多线程。如果直接调用 run 方法就相当于在主线程 main 中调用一个类的普通的方法,并不会重新开启一个线程。
对于线程的开启我们还需要注意的是一个线程类只能调用一次 start 方法否则会抛出IllegalThreadStateException 异常。
3.Thread类常用方法
start():1.启动当前线程 2.调用线程中的 run 方法
run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread():静态方法,返回执行当前代码的线程
getName():获取当前线程的名字
setName():设置当前线程的名字
yield():主动释放当前线程的执行权
join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
stop():过时方法。当执行此方法时,强制结束当前线程。
sleep(long millitime):线程休眠一段时间(毫秒)
2.实现Runable接口
class 线程类 implements Runable{
public void run(){
线程的任务
}
}
public static void main(String[] args){
线程类 线程类对象 = new 线程类();
线程类对象.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Windows implements Runnable{ private int ticket=100; @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"正在卖第"+(101-ticket)); ticket--; } } public static void main(String[] args) { Windows w = new Windows(); Thread t1 = new Thread(w,"窗口1"); Thread t2 = new Thread(w,"窗口2"); Thread t3 = new Thread(w,"窗口3"); t1.start(); t2.start(); t3.start(); } }
3.实现Callable接口
Callable与Runable类似,只是Callable的call方法有返回值
FutureTask可以用于接收Callable对象
FutureTask还提供了一个get方法用于拿取返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Model4 { public static void main(String[] args) { Th1 t = new Th1(); FutureTask futureTask = new FutureTask(t); FutureTask futureTask2 = new FutureTask(t); new Thread(futureTask).start(); new Thread(futureTask2).start(); } } class Th1 implements Callable { private int num = 1; @Override public Object call() throws Exception { while (true){ synchronized (this){ notify(); if (num <= 20){ System.out.println(Thread.currentThread().getName()+"--->"+num); num++; wait(); }else { break; } } } return null; } }
3.线程的调度模式
cpu会给线程分配时间片,一但线程得到了时间片,他就可以执行,哪个线程抢到时间碎片,哪个就先执行,没有抢到时间片的就处于等待状态。
多线程的调度方式有两种即分时调度模式和抢占调度模式,其中分时调度模式是指让所有线程轮流获得 cpu 的使用权,并且分配的使用时间是平均的,而抢占模式则是指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
3.1线程的周期
4.线程安全
前面我们去实现了抢票案例,但是我们发现窗口一、窗口二、窗口三在售卖同一张票,造成这种现象主要是一个线程还没结束,另外一个线程就参与进来,从而造成三个窗口操作同一张票从而出现重票。
对于上述三个窗口售票异常我们也可以称之为线程安全问题,也就是当多个线程去操作共享数据的时候出现了共享数据的冲突,此时线程时不安全的。
4.1使用线程安全类
4.2Synchronized同步锁
这个关键字可以修饰方法或代码块。
synchronized(锁对象){
代码
}
修饰方法:写在方法的返回值前。这样该方法就称为同步方法,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。
public synchronized void fun(){
//要同步的代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30//同步代码块 public class Model8 { public static void main(String[] args) { WindowSal sal = new WindowSal(); Thread thread1 = new Thread(sal,"窗口一"); Thread thread2 = new Thread(sal,"窗口二"); Thread thread3 = new Thread(sal,"窗口三"); thread1.start(); thread2.start(); thread3.start(); } } class WindowSal implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ synchronized (this){ if (ticket > 0){ System.out.println(Thread.currentThread().getName()+"正在售卖第 "+(100-ticket+1)+"张票"+"还剩下:"+(ticket-1)+"张票。"); ticket--; }else { break; } } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//同步方法 public class Windows { public static void main(String[] args) { WindowSal sal = new WindowSal(); Thread thread1 = new Thread(sal,"窗口一"); Thread thread2 = new Thread(sal,"窗口二"); Thread thread3 = new Thread(sal,"窗口三"); thread1.start(); thread2.start(); thread3.start(); } } class WindowSal implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ sal(); if (ticket<=0){ break; } } } private synchronized void sal(){ if (ticket > 0){ System.out.println(Thread.currentThread().getName()+"正在售卖第"+(100-ticket+1)+"张票"+"还剩下:"+(ticket-1)+"张票。"); ticket--; }else { return; } } }
Synchronized特点
1)原子性,里面的代码不可分割,一起执行,一起不执行
2)有序性:被包裹的代码在多线程的条件下,只能由一条线程访问
3)可见性:每次操作其中的数据时,操作的是副本,而不是本体
4.3Lock
Lock();加锁
trylock(long 时间,时间单位);跟Lock();一样,加入了时间,但是有返回值,规定时间内加锁成功返回true,失败返回flase
unLock();释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Model3 { public static void main(String[] args) { T2 t1 = new T2("线程一"); T2 t2 = new T2("线程二"); t1.start(); t2.start(); } } class T2 extends Thread{ private static Lock lock = new ReentrantLock(); private static Condition c1 = lock.newCondition(); private static int num = 1; T2(String name){ super(name); } @Override public void run() { while (true){ try { lock.lock(); c1.signal(); if (num <= 20){ System.out.println(getName()+"--->"+num); num++; c1.await(); }else { break; } }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } } }
线程通信:利用wait()、join()、sleep()、notify()等方法实现线程中的通信
wait()-----使当前线程进入阻塞状态,并且释放锁,wait()需要抛出一个异常InterruptedException
notify()-----用于唤醒另外一个被wait()的线程。notifyall()----用于唤醒所有wait()线程。
wait()和notify()需要放在Synchronized同步方法或者代码块里面,通过锁对象调用wait()和notify()
wait();和sleep();区别
1.sleep();不会释放锁,wait();会释放
2.sleep();到时间自动苏醒,wait();需要手动唤醒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40public class Tongxin { public static void main(String[] args) { Tt tt1 = new Tt("线程一"); Tt tt2 = new Tt("线程二"); tt1.start(); tt2.start(); } } class Tt extends Thread { private static int num = 1; private static Object o = new Object(); public Tt() { } public Tt(String name) { super(name); } @Override public void run() { while (true) { synchronized (o) { o.notify();//-----notify()用于唤醒另外一个被wait()的线程 if (num <= 20) { System.out.println(getName() + num); num++; try { o.wait();// ---使当前线程进入阻塞状态,并且释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }
Lock和Synchronized区别
1、Synchronized是关键字,Lock是一个接口
2、Synchronized的加锁和释放锁是自动的,Lock则相反
3、Lock可以监听到它的锁状态
4、Synchronized会造成死锁,Lock则不会
4.4死锁
如有两个人吃西餐,必须要有刀和叉才能吃饭,只有一副刀叉。
如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己的,
这时就会造成死锁的局面,既不结束,也不继续。
造成死锁的原因:Synchronized使用不当
解决方法:1.改变加锁顺序 2.使用Lock锁
5.线程池
线程池是一种基于池化技术思想来管理线程的工具。在线程池中维护了多个线程,由线程池统一的管理调配线程来执行任务。通过线程复用,减少了频繁创建和销毁线程的开销。
最后
以上就是矮小冬瓜最近收集整理的关于Java-多线程1.几个概念2.多线程的创建3.Thread类常用方法3.实现Callable接口3.线程的调度模式4.线程安全4.4死锁5.线程池的全部内容,更多相关Java-多线程1.几个概念2.多线程内容请搜索靠谱客的其他文章。
发表评论 取消回复