1.基础概念
什么是线程:
线程就是程序中单独顺序的流控制。线程本身不能运行,它只能用于程序中。它只能使用程序内的资源
什么是多线程:
多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务。多线程的目的是最大限度地利用CPU资源。
java中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。
进程:执行中的程序。(程序是静态的,进程是动态的)。
线程和进程的区别
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能相互影响。
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
线程的实现
在java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。
1.继承Thread类并重写run方法。
2.通过定义实现Runnable接口的类进而实现run方法。
将我们希望线程执行的代码放到run方法中,然后通过调用start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。当某个类继承了Thread类之后,这个类就是线程类了。如果直接调用run方法,那它就是一个普通的类,即便它继承了Thread。
一个进程至少要包含一个线程。对于单核CPU,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程同时执行。对于双核或双核以上的CPU来说,可以真正做到微观并行。
第一种方式的实现代码
public class ThreadTest {
public static void main(String[] args){
Thread1 t1 = new Thread1();
t1.start();
}
}
class Thread1 extends Thread{
@Override
public void run(){
for(int i = 0;i <100;i++){
System.out.println("hello world: " + i);
}
}
}
第二种实现方式,代码如下:
public class ThreadTest2 {
public static void main(String[] args){
Thread t1 = new Thread(new MyThread());
// Thread t1 = new Thread(new Runnable(){
//
// @Override
// public void run() {
// for(int i=0;i<100;i++){
// System.out.println("hello: " + i);
// }
// }
// });
t1.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("hello: " + i);
}
}
}
Thread源码分析
(1)Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法。
(2)当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread—number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)。
(3)当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做。
(4)当使用第二种方式来生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())来生成线程对象,这时的线程对象的run方法会调用MyThead类的run方法,这样我们自己编写的run方法就可以执行了。
总结:
(a)两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。
(b)在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应用第二种方法来构造,即实现Runnable接口。
(c)线程的消亡不能通过调用一个stop()命令,而是让run()方法自然结束。
关于成员变量与局部变量:如果一个变量时成员变量,那么多个线程对同一对象的成员变量进行操作时,它们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
停止线程的方式:不能使用Thread类的stop方法来停止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
不能依靠线程的优先级来决定线程的执行顺序。
多线程的同步
为什么要引入同步机制
在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁
synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
示例代码:
public class ThreadTest4 {
public static void main(String[] args){
Example example = new Example();
Thread t1 = new TheThread(example);
Thread t2 = new TheThread2(example);
t1.start();
t2.start();
}
}
class Example{
public synchronized void execute(){
for (int i=0;i<20;i++){
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println("example: " + i);
}
}
public synchronized void execute2(){
for (int i=0;i<20;i++){
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println("world: " + i);
}
}
}
class TheThread extends Thread{
private Example example;
public TheThread(Example example){
this.example=example;
}
@Override
public void run(){
this.example.execute();
}
}
class TheThread2 extends Thread{
private Example example;
public TheThread2(Example example){
this.example=example;
}
@Override
public void run(){
this.example.execute2();
}
}
执行结果是:execute方法执行完了之后execute2方法才能执行。
如果某个synchronized方法是static的,那么当线程访问方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完后另一个线程才开始执行。
synchronized块,写法:
private Object object = new Object();
public void execute(){
synchronized (object){
for (int i=0;i<20;i++){
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println("example: " + i);
}
}
表示线程在执行时会对object对象上锁。
synchronized块和synchronized方法的比较:
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。
多线程间的通信
wait与notify方法都是定义在Object类中,而且是final的,因此会被所有的java类所继承并且无法重写。这两个方法要求在调用线程时应该已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁。
另一个会导致线程暂停的方法就是Thread类的sleep方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
多线程间的通信示例
先建一个Sample类,里面有一个增加和减少两个方法,还有一个字段。
public class Sample {
private int number;
public synchronized void increase() {
while(0!=number){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
number++;
System.out.println(number);
notify();
}
public synchronized void decrease(){
while(0==number){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
number--;
System.out.println(number);
notify();
}
}
再建两个线程,分别是增加和减少一个数的线程:
public class IncreaseThread extends Thread {
private Sample sample;
public IncreaseThread(Sample sample) {
this.sample = sample;
}
@Override
public void run(){
for(int i = 0;i < 20; i++){
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
sample.increase();
}
}
}
public class DecreaseThread extends Thread {
private Sample sample;
public DecreaseThread(Sample sample) {
this.sample = sample;
}
@Override
public void run(){
for(int i = 0;i < 20; i++){
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
sample.decrease();
}
}
}
最后是写一个主函数来进行测试
public class MainTest {
public static void main(String[] args){
Sample sample = new Sample();
Thread t1 = new IncreaseThread(sample);
Thread t2 = new DecreaseThread(sample);
Thread t3 = new IncreaseThread(sample);
Thread t4 = new DecreaseThread(sample);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
测试结果:
01或10不停地交叉出现