Java基础-知识点总结-Java多线程

 

Java多线程

进程:

 

       是一个正在运行的程序,比如正在运行的迅雷,QQ等。

 

   线程:

 

每个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫做一个控制单元,线程就是进程中的一个独立的控制单元;线程控制着进程的运行;一个进程中至少有一个线程。Java虚拟机允许应用程序并发地运行多个执行线程。

 

比如:JVM启动时会运行一个进程java.exe该进程中至少有一个线程负责java程序的执行,并且这个线程运行的代码存在于main方法中,该线程成为主线程。除了主线程外,还有负责垃圾回收机制的进程。

 

 

class Test {
	public static void main(String[] args) // 主线程
	{
		for (int i = 0; i < 4000; i++) {
			System.out.println("Hello World!");
		}
	}
}

 

 

 

 

创建线程

 

       创建新执行线程有两种方法:

 

       方法一:将类声明为Thread的子类。该子类应重写Thread类的run方法。接下来可以分配并启动该子类的实例。

 

class Demo extends Thread // 定义子类继承Thread
{
	public void run() // 复写run()方法,存储自定义代码
	{
		for (int i = 0; i < 30; i++) {
			System.out.println("Demo run" + i);
		}
	}
}

class ThreadDemo {
	public static void main(String[] arge) {
		Demo d = new Demo(); // 创建子类对象,创建一个线程
		d.start(); // 调用start()方法:启动线程;调用run()方法
		d.run(); // 仅仅是对象调用方法,没有开启新的线程
		for (int i = 0; i < 30; i++) {
			System.out.println("Hello World" + i);
		}
	}
}

 

 

 

 

       方法二:声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。

 

  1. 定义类实现Runnable接口

  2. 覆盖Runnable接口中run()方法,将线程要运行的代码存放在该run方法中

  3. 通过Thread类建立线程对象

  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;因为自定义的run()方法所属对象是Runnable接口的子类对象,而又要线程去执行指定对象的run()方法,所以要明确该run()方法所属的对象

  5. 调用Thread类的start()方法开启线程并调用Runnable接口子类的run()方法

 

//定义类实现Runnable接口
class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆盖Runnable接口中的run()方法
		while (true) {
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName()
						+ " sale..." + ticket--);
			}
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket t = new Ticket();
		// 创建线程
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		// 开启线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

 

 

 

 

         两种创建线程的方式的区别:

 

                     第一种方式:直接继承Thread类创建对象,线程代码存放在Thread子类的

 

                     run()方法中。Thread子类无法再从其它类继承(java语言单继承);编写简

 

                    单,run()方法的当前对象就是线程对象,可直接操作。

 

                     第二种方式:使用Runnable接口创建线程,线程代码存放在Runnable接口

 

实现的子类的run()方法中。可以将CPU,代码和数据分开,形成清晰的模

 

;线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法;

 

有利于保持程序的设计风格一致

 

           在实际应用中,几乎都采用第二种方式,第二种能避免单继承的局限性

 

        *从运行的结果中可以发现:

 

             发现运行结果每次都不一样,因为多个线程都在获取cpu的执行使用权,cpu

 

             执行到谁,谁就运行。在某一时刻,cpu(多核除外)只能有一个程序在运

 

             行,由于cpu在做着快速的切换,一大到看上去是在同时运行的效果。可以

 

             得到多线程的一个特性:随机性。

 

       为什么要覆盖run()方法呢?

 

               Run()方法:用于存储要运行的代码,

 

               Start()方法:用于开启线程

 

       线程的四种状态

 

     获取线程对象及名称

 

                    线程都有自己默认的名称名称的格式:Thread-编号编号从0开始

 

                    1、currentThread():静态方法,用于获取当前正在执行的线程对象的引用

 

                    2、GetName():用户与获取当前线程对象的名称

 

3、currentThread().getName()和this.getName()作用相同,都用于获取当前线程对象的名称

 

4、setName():用于给当前线程对象设置名称或者调用Thread类的构造方法设置当前线程对象的名称

 

class Demo extends Thread {
	public Demo(String name) {
		super(name);
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			// 通过调用this.getName()方法获取该线程对象的名称
			System.out.println(this.getName() + "  run…" + i);
			// 通过静态方法currentThread()方法获取当前线程对象
			// 通过currentThread.getName()方法获取当前线程对象名称
			System.out.println(Thread.currentThread().getName() + "  run…" + i);
		}
	}
}

class ThreadTest {
	public static void main(String[] args) {
		Demo t1 = new Demo("线程1");
		Demo t2 = new Demo("线程2");
		t1.start();
		t2.start();
	}
}

 

 

 

 

 

 

     多线程的安全问题

 

class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆盖Runnable接口中的run()方法
		while (true) {
			if (ticket > 0) {
				try {
					Thread.sleep(10);
				} catch (Exception e) {
				}
				System.out.println(Thread.currentThread().getName()
						+ " sale..." + ticket--);
			}
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket t = new Ticket();
		// 创建线程
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		// 开启线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}//运行结果出现了0,-1,-2等的错票,多线程出现了安全问题

 

 

 

 

问题原因

 

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据错误。

 

解决办法:

 

               对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其

 

              他线程不可以参与执行。

 

                           Java对于多线程的安全问题提供了专业的解决方案:同步代码快

 

                           Synchronized(对象){

 

                                  需要同步的代码

 

                           }

 

           代码应修改为:

 

class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆盖Runnable接口中的run()方法
		Object obj = new Object();
		while (true) {
			synchronized (obj) {
				if (ticket > 0) {
					try {
						Thread.sleep(10);
					} catch (Exception e) {
					}
					System.out.println(Thread.currentThread().getName()
							+ " sale..." + ticket--);
				}
			}
		}
	}
}

 

 

 

 

 

     多线程同步代码块

          Synchronized(对象){

                      需要同步的代码

               }

               对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使拥有cpu

               的执行权,也进不去,因为没有持有锁。

               同步的前提:

  1. 必须有两个或有两个以上的线程

  2. 必须是多个线程使用同一个锁

                    好处:解决了多线程的安全问题

                    弊端:多个线程需要判断锁,较为消耗资源

           多线程同步函数

               Public synchronized 返回值类型方法阿明(){方法体}

class Bank {
	private int sum;

	public synchronized void add(int n) {
		sum = sum + n;
		System.out.println("sum=" + sum);
	}
}

class Cus implements Runnable {
	private Bank b = new Bank();

	public void run() {
		for (int i = 0; i < 3; i++) {
			b.add(100);
		}
	}
}

class BankDemo {
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}

 

同步函数用的锁是this

 

因为函数需要被对象调用,那么函数都有一个所属的对象引用,就是this

验证同步函数用的锁是this:

思路:使用两个线程来买票,一个线程在执行同步代码快中所用的锁是obj,一个线程在执行同步函数中,两个线程都在执行买票动作。

class Ticket implements Runnable {
	private int tick = 100;
	Object obj = new Object();
	public Boolean f = true; // 用于区分第一个线程执行同步代码块,第二

	// 个线程在执行同步函数
	public void run() {
		if (f) {
			while (true) {
				synchronized (obj) {
					if (tick > 0) {
						try {
							Thread.sleep(10);
						} catch (Exception e) {
						}
						System.out.println(Thread.currentThread().getName()
								+ "…code " + tick--);
					}
				}
			}
		} else {
			while (true)
				show();
		}
	}

	public synchronized void show() {
		if (tick > 0) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {
			}
			System.out.println(Thread.currentThread().getName() + "…show…"
					+ tick--);
		}
	}
}

class ThisLockDemo {
	public static void main(String[] args) {
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try {
			Thread.sleep(10);
		} catch (Exception e) {
		}
		t.f = false;
		t2.start();
	}
}

运行结果出现了0号票,说明程序不安全,两个线程所用的锁不是同一个锁

而把同步代码块中对象参数改为’this’,则运行结果没有出现不符合的票,说明

两个线程用的是同一个锁,所以同步函数所用的锁是this

静态同步函数所用的锁是该方法所在类字节码文件对象,即类名.class

如果把上面代码中的同步函数用static修饰,则运行结果又会出现0号票,说

明静态同步函数所用的锁不是this,而把同步代码块中的对象参数改

为’Ticket.class’后,运行结果没有出现不合法的票,所以说明两个线程用的锁是

同一个锁,所以静态同步函数所用的锁是本类类名.class

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值