线程与进程的区别
- 进程可以理解成一个正在运行的程序,进程占有独立的内存空间。如:打开QQ。
- 线程是进程里的一个个分支,共享一个内存空间,线程是并发执行的,如果一个进程内不存在任何线程,那么这个进程会消失。
如果一个进程同时执行多个线程,那么就是多线程。如打开QQ的两个功能
线程有几个状态
- 新建:当一个 Thread 类或其子类被创建声明后,新生的线程对象就处于新建状态。
- 就绪:当处于新建状态的线程对象调用 start 方法以后,它就进入线程队列等待 cpu 时间片,此时线程对象已经具备了运行条件,只是没有得到 cpu 资源。
- 运行:处于就绪状态的线程对象获得 cpu 资源以后就可以进入运行状态,run 方法 定义了线程的任务。
- 阻塞:在某些特殊情况下,被人为挂起或执行输入输出操作的时候,线程对象让出 自己的 cpu 并终止任务进入阻塞状态。
- 死亡:线程对象完成了它的线程任务或者被强制退出、发生异常等,线程对象就进 入死亡状态。
线程调度
分时调度:线程轮流使用CPU,每一个线程使用到的时间是平均的。
抢占式调度:顾名思义,哪个线程抢到就是谁的,可以设置优先级,增加或减少某个线程获取到的概率,java用的就是抢占式调度。
调度不会增加程序的运行速率,因为在某个时刻CPU一个核心只能处理一个线程,来回切换让用户感觉好像是在同时执行,但是这样会提高CPU的运行效率,增加CPU的使用率。
并发与并行
并发:线程在同一个时间段发生。即一个cpu执行多个任务。
并行:线程在同一时刻发生。即多个cpu执行多个任务。
创建多线程
可以通过Thread 类和 Runable 、 Callable 接口去实现。继承 Thread 类或者去实现 Runable 、 Callable 接口。(extend Thread / implements Runable/implements Callable)
Thread继承类创建
public class ThreadCreat1 {
public static void main(String[] args) {
Myhread1 myhread1=new Myhread1();
//myhread1.start();//用于开启一个线程,并且start方法只能调用依次
//start有两个功能:1.让线程进入等待队列 2.调用run方法
myhread1.run();
myhread1.start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"线程打印--->"+i);
}
}
}
class Myhread1 extends Thread{
//run方法里面存放的是线程任务
public void run(){
for (int i=0;i<10;i++){
//Thread类提供了一个静态方法名字叫做getName()用于获取线程名称
System.out.println(getName()+"线程打印--->"+i);
}
}
}
我们通过继承Tread创建线程,利用start()方法开始线程,这里注意:run()方法代表mythread1对象调用类方法开始运行。而start()方法才是表示开始线程。运行结果也大不相同。
run(): start():
这里可以看出线程调度是不可控的,所以线程执行也是随机的,不可控制的。
那么什么是线程调度:
线程调度有两种:
1.分时调度
分时调度就是所有线程轮流拥有(使用)cpu的使用权,平均分配每个线程占用cpu的时间
2.抢占式调度
抢占式调度就是优先让优先级高的线程使用cpu,如果线程的优先级相同,则会随机选择一个,所以谁的优先级高,谁抢夺cpu的几率就越大,从而优先级高的占用cpu的时间会更长,Java为抢占式调度.
抢占式调度使cpu会在多个进程中、多个线程中来回跳转,其速度非常快。
Thread类的常见方法:
- start():1.启动当前线程 2.调用线程中的 run 方法(一个线程只能调用依次start方法)
- run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方
- 法中(不会开启新的线程)
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():(从运行到就绪)可以让当前线程释放时间碎片,进入就绪状态,进入就绪状态,重新等待时间碎片
- join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执 行完毕以后,该线程才继续执行下去
- stop():过时方法。当执行此方法时,强制结束当前线程。
- sleep(long millitime):线程休眠一段时间
线程优先级
给线程制定一个优先级,高优先级的线程抢占 cpu 的概率更高,它们被分为 10 个等级,其中最为常用的是最高优先级 10 、最低优先集 1 、默认优先级 5 ,如下所示:
- 最低优先级 1:Thread.MIN_PRIORITY
- 最高优先级 10:Thread.MAX_PRIORITY
- 普通优先级 5:Thread.NORM_PRIORITY
如果我们要去查看和设置一个线程的优先级,我们可以通过 Thread 类提供的 getPriority()和 setPriority() 方法,其中 getPriority() 用于查看一个线程的优先级,而 setPriority() 则是用于去设置一个线程的优先级。
![](https://i-blog.csdnimg.cn/blog_migrate/aaee1571d49e098bcb5b7389048efe5e.png)
这里要注意,这和HTML5中的index级别不同,它不代表级别越高就一定会总是优先执行,它仅代表比它优先级低的线程有更大的几率获得时间碎片,启动线程。
Runable接口
Runable和Thread没什么区别,本质上只是实现关系
public class Model {
public static void main(String[] args) throws InterruptedException {
RunableTest runableTest = new RunableTest();
Thread thread = new Thread(runableTest, "线程一");
Thread thread1 = new Thread(runableTest, "线程二");
thread.start();
thread.join();
thread1.start();
}
}
class RunableTest implements Runnable {
@Override
public void run() {
/*
currentThread()Thread提供的方法,返回当前线程类对象
*/
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
这里可以看出,Runable接口实现线程是通过借用了Thread来实现的,Runable必须使用run方法。我们通过RunableTest类实现接口Runable,重写run方法,再在main方法中创建RunableTest对象,这里注意因为是通过接口实现,所以不能直接调用start()方法,要将RunableTest对象放进Thread类创建的空间中,间接通过Thread实现线程。
Runnable实现的线程可以放入线程池,Thread不行。
Callable接口
Callable 和 Thread 类、 Runable 接口一样都是创建线程的方式,其中 Thread 类和 Runable接口通过 run 方法实现线程的任务, Callable 接口通过 callable 方法实现线程的任务,并且callable 方法是有返回值的。
线程安全
线程安全问题也就是当多个线程去操作 共享数据的时候出现了共享数据的冲突,此时线程时不安全的。
比如:售票问题。三个窗口共享100张票(即三个线程),100张票就是三个线程所共享的数据,因为线程进行是随机不可控的,谁先抢到时间碎片就先进行,但此时会出现某一线程在处理数据时,另外一个线程又加入进来处理共享数据,此时会造成多个线程同时操作同一张票即重票,这个时候线程就属于不安全。
那么怎样解决线程安全问题呢?
需要采取一定措施,也就是说一个线程在操作数据的时候其它线程不能参与进来,只能当前线程某个操作完成以后才可以 让其它线程参与进来即线程同步。
Synchronized与Lock
这两个都可以用来解决线程安全问题,利用他们实现锁住功能,即将其内的内容锁住,只允许一个线程完成后另外一个才能进来。
Synchronized可同步代码块或方法,它是自动锁的
同步代码块:
public class Test000 {
public static void main(String[] args) {
Object o=new Object();
Station1 s1=new Station1("售票口1",o);
Station1 s2=new Station1("售票口2",o);
Station1 s3=new Station1("售票口3",o);
s1.start();
s2.start();
s3.start();
}
}
class Station1 extends Thread{
private Object o;
public Station1() {
}
public Station1(String name, Object o) {
super(name);
this.o = o;
}
static int j=1;
@Override
public void run(){
while (true){
synchronized (o) {
if(j>100){
System.out.println("售罄");
break;
}
System.out.println(getName() + "正在售卖第" + j + "张票" + " " + "还剩下" + (100 - j) + "张票");
j++;
}
}
}
}
将售票手续逻辑实现的代码块同步锁,此时只有当线程完成里面的操作,另一个线程才能进来。这里注意:
synchronized()里面的参数列表在Thread类进行线程时可以:
1.在Statino类中定义私有private Object o;创建Station含参构造,在main方法中创建Object对象o和Station类对象si,将Object对象放入Station类对象空间,此时synchronized()参数列表为对象o。
2.直接在Station类中定义private static Object o=new Object();,将o放入synchronized()中。
3.直接在synchronized()中放入Station.class//(0类.class)
synchronized()里面的参数列表在利用Runable接口实现线程时可以:
也有三种方法,前两种和Thread中的一样。
第三种:在synchronized()里面直接放this。
但synchronized会造成死锁。
public class DeathSynchronized {
public static void main(String[] args) {
Thread007 t1 = new Thread007("线程一",1);
Thread007 t2 = new Thread007("线程二",2);
t1.start();
t2.start();
}
}
class Thread007 extends Thread{
private static Object object1 = new Object();
private static Object object2 = new Object();
private int i;
public Thread007(String name,int i){
super(name);
this.i=i;
}
@Override
public void run() {
if (i==1){
synchronized (object1){
System.out.println(getName()+"拿到了锁1");
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"尝试拿取锁2");
synchronized (object2){
System.out.println(getName()+"拿到了锁2");
}
}
}else {
synchronized (object2){
System.out.println(getName()+"拿到了锁2");
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"尝试拿取锁1");
synchronized (object1){
System.out.println(getName()+"拿到了锁1");
}
}
}
}
}
因为线程一拿到锁一,线程一拿到锁二,但此时两个线程又要互相访问双方的锁,锁得不到释放,两线程就一直处于等待状态,造成死锁
Lock
它有两种方式:
一:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest1 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
private static Lock lock=new ReentrantLock();
@Override
public void run(){
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"----->"+i);
}
}finally{
lock.unlock();//lock和unlock之间是被保护的代码
}
}
}
lock是手动形式的。
我们在操作 Lock 锁需要注意的是 Lock 锁需要手动释放,如果发生了异常锁也不会被释放,需要我们手动释放,对此我们在使用 Lock 锁时就需要将代码放在放在 try-finally 异常处理机制中。
lock.lock();与lock.unlock();中的内容是被保护的代码。
trylock
tryLock(long time, TimeUnit unit) 方法和 tryLock() 方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回 false 。如果一开始拿到锁或者在等待期间内拿到了锁,则返回 true 。
public class LockTest2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2("线程1");
MyThread2 myThread20 = new MyThread2("线程2");
MyThread2 myThread21 = new MyThread2("线程3");
myThread2.start();
myThread20.start();
myThread21.start();
}
}
class MyThread2 extends Thread{
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
private static Lock lock=new ReentrantLock();
@Override
public void run(){
boolean flag= false;
try {
flag = lock.tryLock(20000, TimeUnit.MICROSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(flag){
try {
System.out.println(getName()+"获得锁");
}finally {
System.out.println(getName()+"释放锁");
lock.unlock();
}
}else{
System.out.println(getName()+"加锁失败!");
}
}
}
tryLock( ):和Lock ()方法一样也是用于加锁,只是它有返回值:
若加锁成功则返回true,否则返回false
需要注意的是若加锁失败就不能去unLock解锁
tryLock(Long time,TimeUnit unit) :
和tryLock类似,只是该方法加入了时间即规定时间内加锁成功就返回true,
否则返回false该方法会抛出一个异常即InterruptedException
线程通信
一个线程可以去完成属于自己的任务,如果我们想要多个线程按照规则去协同执行任务就需要使用到线程通信。利用wait()、join、sleep等方法去实现线程之间的一个交互wait和notify配合使用。wait ()利Inotify必须放在Synchronized同步方法或者代码块里面 通过锁对象去调用wait利notify方法。
notifyalL ()利Inotify ():notify( )---用于唤醒单个线程,notifyaLL()---用于唤醒所有线程。
wait和sleep区别:
sleep不会释放锁,wait会
sleep到时间后自动唤醒,wait需要notify手动huanx
public static void main(String[] args) {
Object o=new Object();
T t1=new T("线程1",o);
T t2=new T("线程2",o);
t1.start();
t2.start();
}
}
class T extends Thread {
private static int num=1;
private Object o;
public T() {
}
public T(String name, Object o) {
super(name);
this.o = o;
}
//private static Object object=new Object();
@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;
}
}
}
}
}
*线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。
线程池的优点:降低资源消耗、提高响应速度、方便管理;线程可以复用、可以控制最大并发数、可以管理线程。