一、创建线程的两种方法
①继承Thread类
class MyThread extends Thread{
@Override
public void run(){
}
}
MyThread mt = new MyThread();
mt.start();
②实现Runnable接口
class MyThread implements Runnable{
@Override
public void run(){
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt);
td.start();
二、两种方式的比较(火车站买票)
① Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。
②Runnable的代码可以被多个线程共享,适合于多个线程处理同一个资源的情况。
class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run(){
while(true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"sale"+tick--);
}
}
}
}
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
存在的线程安全问题:会出现负票
分析:
ticket是t1、t2、t3共享的变量。当多个线程在操作共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。
解决方法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方法:同步代码块
synchronized(对象){
需要同步的代码
}
class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run(){
while(true){
synchronized(this){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"sale"+tick--);
}
}
}
}
}
注:同步函数public synchronized void add()
同步函数用的锁是this
三、懒汉式
class Single {
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null){
s = new Single();
}
return s;
}
}
经过上面的学习,发现这样写懒汉式,如果多线程访问时,就会出现安全问题,两个线程同时到达s=new Single()
如果使用同步函数:
class Single {
private static Single s = null;
private Single(){}
public static synchorized Single getInstance(){
if(s==null){
s = new Single();
}
return s;
}
}
但是这样的话,会影响执行的效率,改为如下:
class Single {
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchorized(Single.class){
if(s==null){
s = new Single();
}
}
}
return s;
}
}
四、线程同步
其实就是多个线程在操作同一个资源,但是操作的动作不同。
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r;
Input(Res r)
{
this.r = r;
}
public void run(){
int x = 0;
while(true){
if(x==0){
r.name = "mike";
r.sex = "man";
}else{
r.name = "丽丽";
r.sex = "女";
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
private Res r;
Output(){
this.r = r;
}
public void run(){
while(true){
System.out.println("nam:"+r.name+"..."+"sex:"+r.sex);
}
}
}
class InputOutputDemo{
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
运行上面的程序会发现有安全性问题。
对于这种多个线程操作同一个资源,但是不是同一个操作,该如何同步呢?
解决方法:
class Output implements Runnable{
private Res r;
Output(){
this.r = r;
}
public void run(){
while(true){
synchorized(r/Input.class/Output.class/Res.class){
System.out.println("nam:"+r.name+"..."+"sex:"+r.sex);
}
}
}
}
class Input implements Runnable{
private Res r;
Input(Res r)
{
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchorized(r/Input.class/Output.class/Res.class){
if(x==0){
r.name = "mike";
r.sex = "man";
}else{
r.name = "丽丽";
r.sex = "女";
}
x = (x+1)%2;
}
}
}
}
五、守护线程
在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断
import java.util.concurrent.TimeUnit;
/**
* 守护线程
*/
public class Daemons {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Thread d = new Thread(new Daemon());
d.setDaemon(true); //必须在启动线程前调用
d.start();
System.out.println("d.isDaemon() = " + d.isDaemon() + ".");
TimeUnit.SECONDS.sleep(1);
}
}
class DaemonSpawn implements Runnable {
public void run() {
while (true) {
Thread.yield();
}
}
}
class Daemon implements Runnable {
private Thread[] t = new Thread[10];
public void run() {
for (int i=0; i<t.length; i++) {
t[i] = new Thread(new DaemonSpawn());
t[i].start();
System.out.println("DaemonSpawn " + i + " started.");
}
for (int i=0; i<t.length; i++) {
System.out.println("t[" + i + "].isDaemon() = " +
t[i].isDaemon() + ".");
}
while (true) {
Thread.yield();
}
}
}
运行结果:
d.isDaemon() = true.
DaemonSpawn 0 started.
DaemonSpawn 1 started.
DaemonSpawn 2 started.
DaemonSpawn 3 started.
DaemonSpawn 4 started.
DaemonSpawn 5 started.
DaemonSpawn 6 started.
DaemonSpawn 7 started.
DaemonSpawn 8 started.
DaemonSpawn 9 started.
t[0].isDaemon() = true.
t[1].isDaemon() = true.
t[2].isDaemon() = true.
t[3].isDaemon() = true.
t[4].isDaemon() = true.
t[5].isDaemon() = true.
t[6].isDaemon() = true.
t[7].isDaemon() = true.
t[8].isDaemon() = true.
t[9].isDaemon() = true.
六、join、优先级、yield
①join()方法
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。这句话可能有点晦涩,我们还是通过例子去理解:
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}
说明:
上面的有两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
在Father主线程中,通过new Son()新建“子线程s”。接着通过s.start()启动“子线程s”,并且调用s.join()。在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;在“子线程s”运行完毕之后,Father主线程才能接着运行。 这也就是我们所说的“join()的作用,是让主线程会等待子线程结束之后才能继续运行”!
②yield()
暂停当前正在执行的线程对象,并执行其他线程。
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
注意:
yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
class ThreadYield extends Thread{
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i ==30) {
this.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield("张三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
运行结果:
第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。
第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。
sleep()和yield()的区别
sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。