进程和线程的区别
进程:计算机中特定功能的程序在数据集上的一次运行
线程:线程是进程的一个单元 线程是小的,进程是大的
多线程:一个进程中有多个线程在同时运行,如迅雷下载,迅雷软件的运行就是一个进程,在迅雷中可同时下载多个电影,这就是多线程(每一个下载都是一个线程)
JVM是多线程的,在运行JVM时候,后台会运行垃圾回收的线程,来清理没有被引用的对象。
线程的执行原理:
线程的并发执行是通过多个线程不断地切换CPU的资源,速度非常快,实际上同一时间只有一个线程在工作
因为速度非常快,所以所感知到认为是多个线程在并发执行(所以可以认为是同时执行)尽管多个线程不断切换CPU的资源,但速度太快,可认为是同时执行。
为什么要使用多线程?
子线程执行发送请求后,就会自动结束,那么就接收不了服务器响应的数据 子线程是无法通过return语句来返回数据的
所有的代码默认在主线程中运行
为一个任务开启子线程 说明这个任务是比较耗时间的 目的是保证执行该任务的同时其他任务也可以执行
否则,如果该任务执行在主线程,并且比较耗时间,则会出现主线程阻塞。
线程的生命周期
流程:
1.新建:线程被new出来
2.准备就绪:线程具有执行的资格,即线程调用了start()方法,还没有执行权利,因为要抢资源,所以调用了start不一定马上就执行
3.运行:抢到了CPU资源,则可运行
4.阻塞:没有继续执行的资格,sleep()时间到了进入准备就绪状态或notify()唤醒进入准备就绪状态或wait()等待
5.销毁:线程的对象编程垃圾,释放资源当Run()结束,则销毁。
线程的实现:
类 Thread
构造器:
Thread(Runnable target) 用于第二种方法创建线程对象
分配新的 Thread 对象。
Thread(Runnable target, String name) 可指定线程的名字
分配新的 Thread 对象。
创建新执行线程有两种方法。
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
class PrimeThread extends Thread { 用该类new出来的是对象
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
注:该类继承了父类Thread,可用该类来创建线程对象
另一种方法是声明实现 Runnable 接口的类。
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
采用第二种方法的优势:因为java是单继承的,所以如果该类继承了Thread就不能继承其他类,但用第二种方法,实现了Runnable,还可以继承其他类。形成了多线程的共享一个对象
启动线程:
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
并发
并发问题: 如卖火车票,电商网站
解决方案:
针对线程的安全性问题,需要使用同步(就是要加锁,共享资源同一时间之只能一个线程访问)锁,
语法:
synchronized(锁对象){
//操作共享资源的代码
}
同步代码加在
1.代码被多个线程访问
2.代码中有共享的数据
3.共享数据被多条语句操作
private String name ;
private static int tickets = 100;
public Sale(String name) {
super(name);
}
private static Object obj = new Object();
//方法体只能在方法中,不能在外面,if-else语句块一定要加大括号
public void run() {
while(true) {
synchronized (obj) {
if(tickets > 0) {
System.out.println(this.getName()+"正在卖第"+tickets--+"张票");
}
else {
System.out.println("票已卖完");
break;
}
}
}
}
synchronized同步代码块的锁对象可以是任意类对象(线程的实现方式是继承于Thread)这个对象必须是线程类共享的(静态的)
synchronized修饰在方法上
如果是静态方法,synchronized的锁对象就是类的类对象—类锁(synchronized加在线程体内)
synchronized如果加在非静态的对象方法上,那么它的锁是this—对象锁(synchronized加在线程体内)
因为static修饰的方法内不能用this,所以只能用类的类对象来表示锁
如果方法没有static修饰,那么在线程体内就可调用this来作为对象
类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。
同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
两个对象锁,只能运行一个,因为都是同一个对象。但是一个对象锁,一个类锁,都可以执行。
synchronized修饰在一般方法上:
实现Runnable的子类创建的对象,相当于一把钥匙,Thread创建的线程使用这把锁进入同步代码块,如果两个线程共用这个对象,那么同一时间只能由一个线程进入这个同步代码块。如果创建了两个锁对象,两个线程分别运用这钥匙,但最后只能一个线程得到进入两个锁,那么两个线程都可以同时进入同步代码块里面。如果没有明确的锁对象,那么可以创建任意一个锁对象,任何线程都有机会拿这把锁。
synchronized修饰run()
public synchronized void method()
{
// todo
}
public void method()
{
synchronized(this) {
// todo
}
}
以上代码是等价的.
1.synchronized关键字不能继承。
2.在定义接口方法时不能使用synchronized关键字。
3.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
synchronized修饰静态方法:
synchronized修饰的静态方法锁定的是这个类的所有对象。
语法:用synchronized修饰在普通的静态方法上,把静态方法放在synchronized修饰的run的方法体中。synchronized修饰的静态方法锁定的是这个类的所有对象,意思就是说,这个方法是类方法,所有这个类创建出来的对象可作为一个整体,实质上都是同一把锁。
public synchronized static void method() {
//todo
}
public synchronized void run() {
method();
}
注:以上代码如果创建了两个锁对象,那么分别给两个线程运行时,只能一个线程执行,因为两个锁对象是指上为同一把锁。
synchronized修饰在static方法内的语句块中: 修饰在类上 锁是类
public static void method() {
synchronized(SyncThread.class) {
}
}
public synchronized void run() {
method();
}
注:因为修饰在同步代码上,且在静态方法中,那么这个锁对象就是类的所有对象,这个类的所有对象创建实质上都是共用同一把锁。
synchronized修饰代码块的优点:
用synchronized来修饰代码块:synchronized(user){user.test();},
那么这个方法加锁的对象是user这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法是没有影响的,因为他们持有的锁完全不一样。(意思是修饰代码块,锁对象和执行代码的对象不一样,所以其他对象可以访问其他的同步方法)
synchronized的缺陷:
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,
必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,
那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。
总结:
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,
则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,
则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。