黑马程序员-java多线程
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
概念:
1.进程
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
2.线程
一个进程通常包含多个线程,一个线程成为控制单元,也叫执行路径,程序的执行都是线程在操作。线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
注意:开启线程是通过调用start方法来完成的,而并非调用run方法。
线程是程,序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
利弊分析:
java中多线程可以让计算机同时帮我们执行不同的代码,但这样做的后果就是,在计算机中出现过多的线程,从而导致CPU切换周期加大,也就影响了从大体角度来说总体的运行效率。
java中线程的五种状态:
如何创建线程?
1.继承Thread类,并覆盖run方法
public class ThreadDemo {
/**
* @param args
*/
public static void main(String[] args) {
MyThread thread=new MyThread("thread1");
MyThread thread2=new MyThread("thread2");
thread.start();//开启一个线程,并自动调用run方法
thread2.start();
}
}
//实现线程的第一种方式,继承Thread类,并覆盖run方法。
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name=name;
}
//run方法里面封装要开启的线程所要执行的任务代码。
public void run(){
for(int i=0;i<100;i++){
System.out.println(name+"..."+i);
}
}
}
注意:开启线程是通过调用start方法来完成的,而并非调用run方法。
2.实现Runnable接口
public class ThreadDemo2 {
public static void main(String[] args){
MyThread run=new MyThread();
//创建线程时给定Runnable接口。
Thread t=new Thread(run);
t.start();
}
}
//创建线程第二种方式,通过实现Runnable接口。
// 这种方式是解决java中不能多继承的不足
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("开启线程要执行的代码");
}
}
这种方式是为了解决当要开启多线程的类如果继承了别的类,那么就只能采用这种方式。同时这种方式还可以将共享数据独立出来。
多线程存在安全问题:
//多线程安全问题产生的条件:
//1.存在多线程操作共享数据。
//2.操作共享数据的线程代码有多条。(当操作共享数据代只有一条语句时不会有安全问题)
//导致线程安全问题发生的原因:当一个线程在执行操作共享数据的多行代码过程中,其他线程参与了进来,就会导致线程安全问题的发生。
public class ThreadDemo2 {
public static void main(String[] args){
MyThread2 run=new MyThread2();
Thread t=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t.start();
t1.start();
t2.start();
}
}
class MyThread2 implements Runnable{
private int num=100;//多线程操作的共享数据
@Override
public void run() {
//多线程操作共享数据代码
while (true) {
if (num > 0) {
try {
Thread.sleep(100);// 模拟当一个线程在执行操作共享数据代码时,其他线程进入。
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "...sale..." + num--);
}
else
break;
}
}
}
运行结果:
....................Thread-1...sale...2
Thread-2...sale...1
Thread-0...sale...0
Thread-1...sale...-1
Thread-2...sale...1
Thread-0...sale...0
Thread-1...sale...-1
如何解决多线程安全问题?
//解决多线程安全问题:加同步代码块
//同步代码块格式:
//synchronized(对象){}
//加同步的好处:解决了线程安全问题
//加同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。
//同步的前提:同步中必须有多个线程并使用同一个锁。
public class ThreadDemo2 {
public static void main(String[] args){
MyThread2 run=new MyThread2();
Thread t=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t.start();
t1.start();
t2.start();
}
}
class MyThread2 implements Runnable{
private int num=100;//多线程操作的共享数据
private Object object=new Object();//锁对象,锁旗标。
@Override
public void run() {
//多线程操作共享数据代码
while (true) {
synchronized (object) {
if (num > 0) {
try {
Thread.sleep(100);// 模拟当一个线程在执行操作共享数据代码时,其他线程进入。
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "...sale..." + num--);
} else break;
}
}
}
}
public class SynchronizedDemo {
/**
* @param args
*/
public static void main(String[] args) {
MyThread3 run=new MyThread3();
Thread t=new Thread(run);
Thread t2=new Thread(run);
t.start();
t2.start();
}
}
class Bank{
private int sum;
//解决线程安全问题方式二:同步方法
//同步方法,和同步代码块的效果一样,只是同步方法用的同步锁都是this,而同步代码块的锁是任何对象。
public synchronized void add(int num){
sum+=num;
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("sum="+sum);
}
}
class MyThread3 implements Runnable{
Bank bank=new Bank();
@Override
public void run() {
for(int i=0;i<3;i++){
bank.add(100);
}
}
}
注意:静态同步方法的同步锁是该方法所属的字节码文件对象。
多线程中死锁现象:
//死锁:发生场景一般在同步块嵌套。但这种情况也有和谐的时候,也就是说在同步嵌套时并不一定会死锁。
public class DeadLockDemo {
/**
* @param args
*/
public static void main(String[] args) {
Thread t1=new Thread(new Test(true));
Thread t2=new Thread(new Test(false));
t1.start();
t2.start();
}
}
class Test implements Runnable{
private boolean flag;
public Test(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
while(true)
synchronized (MyLock.locka) {
System.out.println("if....locka");
//线程到这里会去拿lockb锁,当如果有其他线程已经占有lockb锁,程序就会在这发生死锁。
//但如果该线程到这里刚好其他线程释放了lockb锁,而他就会得到lockb锁,释放locka锁,程序将继续执行。这就是有可能不会死锁的原因。
synchronized (MyLock.lockb) {
System.out.println("if....lockb");
}
}
}else{
while(true)
synchronized (MyLock.lockb) {
System.out.println("else....lockb");
synchronized (MyLock.locka) {
System.out.println("else....locka");
}
}
}
}
}
//锁
class MyLock {
public static final Object locka=new Object();
public static final Object lockb=new Object();
}
我们在程序中应该尽量避免死锁的发生。
线程的等待唤醒机制:
//线程的等待唤醒机制 wait() notify() notifyAll()
//为什么wait() notify() notifyAll()这些方法必须用在同步中?
//1.因为这些方法都是操作线程状态的方法
//2.因为在调用这些方法前必须明确要操作的是哪个监视器(锁)上的线程。
public class NotifyDemo {
/**
* @param args
*/
public static void main(String[] args) {
Resource r=new Resource();
Output out=new Output(r);
Input in=new Input(r);
Thread t1=new Thread(out);
Thread t2=new Thread(in);
t1.start();
t2.start();
}
}
class Resource{
String name;
String sex;
boolean flag;
}
class Input implements Runnable{
Resource resource;
public Input(Resource resource){
this.resource=resource;
}
public void run(){
int x=0;
while(true){
synchronized (resource) {
if(resource.flag)
try {
//同步锁resource.wait()方法,将持有同步锁resource的线程加到同步锁resource的线程池。
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (x == 0) {
resource.name = "jiava";
resource.sex = "nan";
} else {
resource.name = "夫人";
resource.sex = "女女女女";
}
x = ++x % 2;
resource.flag=true;
//从同步锁resource的线程池中唤醒持有resource同步锁的某个线程。
resource.notify();
}
}
}
}
class Output implements Runnable{
Resource resource;
public Output(Resource resource){
this.resource=resource;
}
public void run(){
while(true){
synchronized (resource) {
if(!resource.flag)
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(resource.name+"....."+resource.sex);
resource.flag=false;
resource.notify();
}
}
}
}
注意:同步中只有一个线程在执行,但活着的线程(具备执行资格不具备执行权)不只一个。
interrupt()方法清除线程的中断状态,强制性的,会抛InterruptedException异常。
wait()和sleep()区别:
1.wait()可以不指定时间,而sleep()必须指定。
2.在同步中,对cpu的执行权和锁的处理不同。
wait()释放执行权和执行资格,释放锁。
sleep()释放执行权和执行资格,不释放锁。