一、线程的创建
1.继承Thread类 class Demo {
public static void main(String[] args) {
Thread t = new Thread();
t.start(); //第三步,调用start()方法
}
}
class MyThread extends Thread { //第一步,定义类并继承Thread类
//...定义一些属性或方法
public void run() { //第二步,重写run()方法
//...
}
}
2.实现Runnable接口
class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t = new Thread(mt); //第三步,将Runnable对象传入Thread类的构造函数
t.start(); //第四步,调用start()方法
}
}
class MyThread implements Runnable { //第一步,定义类并实现Runnable接口
//...定义一些属性或方法
public void run() { //第二步,重写run()方法
//...
}
}
3.匿名内部类的方式简化书写
class Demo {
public static void main(String[] args) {
//第一种方式
new Thread(){
//...
public void run() {
//...
}
}.start();
//第二种方式
new Thread(new Runnable(){
//...
public void run() {
//...
}
}).start();
}
}
二、线程常用方法
1.currentThread : 静态方法,获取当前的线程对象.2.getName, setName : 获取和设置线程的名字.
3.sleep : 静态方法,控制当前线程休眠指定毫秒.
4.setDaemon : 设置指定线程为守护线程,守护线程不会单独运行.
5.join : 当前线程暂停,等待指定线程执行完毕后再继续.
//示例一
public class ThreadDemo1 {
public static void main(String[] args) {
Person p = new Person();
p.setName("张三"); //setName()方法.
p.start();
}
}
class Person extends Thread {
public void run() {
for (int i = 0; i < 30; i++) {
try {
sleep(400); //由于继承 了Thread所以可以直接sleep(),而不用Thread.sleep().
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThread().getName()+i); }
}
}
//示例二
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("A " + i);
try {
Thread.sleep(1000); //可以直接sleep().
} catch(Exception e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread(){
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("B " + i);
try {
Thread.sleep(1000);
} catch(Exception e) {
e.printStackTrace();
}
}
}
};
t2.setDaemon(true); // 设置t2线程为守护线程,则当t1线程结束后,t2也会结束.
t1.start();
t2.start();
}
}
//示例三
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
final Thread t1 = new Thread(){ //被内部类调用,所以要加final
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("A " + i);
try {
Thread.sleep(1000); //可以直接sleep().
} catch(Exception e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread(){
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("B " + i);
try {
Thread.sleep(1000);
if (i == 2)
t1.join();//当前线程暂停, 等待t1线程执行结束之后再继续
} catch(Exception e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
}
}
三 、同步
1.什么是同步在多线程并发访问同一资源时,有可能出现线程安全问题,为了解决这种问提,引出同步技术。
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
2.同步的几种方法
(1)同步方法
如果整个方法内的代码都需要同步,可以在方法钱加上synchronized修饰,由于同步方法默认的同步锁是this作为锁对象,
所以如果需要同步的方法不在同一个类中,则必须改用同步代码块的形式.
(2)同步代码块
将需要同步的代码放在 synchronized(){}代码块中,在小括号中指定锁对象.
(3)JDK5中的同步
使用ReentrantLock类的lock()方法开始同步.
使用ReentrantLock类的unlock()方法结束同步.
注意:解锁的代码unlock()方法一定要执行,尽量放在finally.
//示例一
class Demo {
public static void main(String[] args) {
final Printer p = new Printer(); //匿名内部类调用,需要final修饰
new Thread() {
public void run() {
while(true)
p.print1();
}
}.start();
new Thread(new Runnable(){
public void run() {
for(;;) //无限循环
p.print2();
}
}).start();
}
}
class Printer {
public synchronized void print1() { //同步方法
System.out.print(1);
System.out.print(2);
System.out.println(3);
}
public void print2() {
synchronized(this) { //同步代码块
System.out.print("一");
System.out.print("二");
System.out.println("三");
}
}
}
//以上代码如果没有同步,在一行中有可能出现类似 12三或 一2三 等混乱的情况.
//同步后问题解决.
//示例二
import java.util.concurrent.locks.ReentrantLock;
class Demo {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread(){
public void run() {
while(true)
p.print1();
}
}.start();
new Thread() {
public void run() {
while(true)
p.print2();
}
}.start();
}
}
class Printer {
private ReentrantLock lock = new ReentrantLock(); //建立一个用来同步的锁的对象.
public void print1() {
try {
lock.lock(); //开始同步
System.out.print(1);
System.out.print(2);
System.out.println(3);
} finally {
lock.unlock(); //结束同步
}
}
public void print2() {
try {
lock.lock();
System.out.print("一");
System.out.print("二");
System.out.println("三");
} finally {
lock.unlock();
}
}
}
四、通信
如何通信首先通信的代码必须写在同步代码中.
方式一
使用锁对象来调用wait()和notify().
wait()方法可以控制当前线程等待,知道其他线程调用notify()或者notifyAll()方法才被唤醒.
方式二
先用ReentrantLock调用newCondition()方法获得Condition对象.
然后使用Condition对象的await()方法等待.
使用Condition对象的signal()方法唤醒.
与方式一的区别:可以创建多个Condition对象,不同的线程使用不同的Condition,这样唤醒的时候可以唤醒指定的Condition.
//示例一
class Demo {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread(){
public void run() {
while (true)
try {
p.print1();
} catch(Exception e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
public void run() {
while(true)
try {
p.print2();
} catch(Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
class Printer {
private int flag = 1; //标记应该轮到哪个方法
public synchronized void print1() throws Exception {
if (flag == 2)
wait(); //等待
System.out.print(1);
System.out.print(2);
System.out.println(3);
flag = 2;
notify(); //通知另一个线程继续
}
public synchronized void print2() throws Exception {
if (flag == 1)
wait();
System.out.print("一");
System.out.print("二");
System.out.println("三");
flag = 1;
notify();
}
}
//示例二
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class Demo {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread(){
public void run() {
while (true)
try {
p.print1();
} catch(Exception e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
public void run() {
while(true)
try {
p.print2();
} catch(Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
class Printer {
private int flag = 1; //标记应该轮到哪个方法
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
public void print1() throws Exception {
try {
lock.lock();
if (flag == 2)
c1.await();
System.out.print(1);
System.out.print(2);
System.out.println(3);
flag = 2;
c2.signal();
} finally {
lock.unlock();
}
}
public synchronized void print2() throws Exception {
try {
lock.lock();
if (flag == 1)
c2.await();
System.out.print("一");
System.out.print("二");
System.out.println("三");
flag = 1;
c1.signal();
} finally {
lock.unlock();
}
}
}
//结果是123 一二三交替运行.
五、练习
1.写一个延迟加载的单例设计模式 class SingletonDemo {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance(); // 第一次调用时创建对象
Singleton s2 = Singleton.getInstance();
System.out.println(s1);
System.out.println(s2); //验证一下是否单例,结果两个地址相同.验证成功.
}
}
//延迟加载的,又称为懒汉式
class Singleton {
private static Singleton s = null;
private Singleton(){}
public static synchronized Singleton getInstance() {
if (s == null)
s = new Singleton();
return s;
}
//... 别的属性或者方法
}
//一般的,称为饿汉式
class Singleton {
private static Singleton s = new Singleton(); //2.类内部创建一个对象, 使用成员变量引用. 用static修饰在类加载后初始化. 防止类外修改加上private
private Singleton(){} //1.私有化构造函数,阻止外部创建对象
public static Singleton getInstance() { //3.提供一个get方法获取对象, 为了让类外直接使用加上static
return s;
}
}
2.写一个买票的示例
import java.util.concurrent.locks.ReentrantLock;
class TicketSellerDemo {
public static void main(String[] args) {
TicketSeller ts1 = new TicketSeller();
TicketSeller ts2 = new TicketSeller();
TicketSeller ts3 = new TicketSeller();
TicketSeller ts4 = new TicketSeller();
ts1.setName("张三");
ts2.setName("李四");
ts3.setName("王五");
ts4.setName("赵六");
ts1.start();
ts2.start();
ts3.start();
ts4.start();
}
}
class TicketSeller extends Thread {
private static int ticket = 100;
private static ReentrantLock lock = new ReentrantLock(); // 多条线程必须使用同一个锁对象, 才能同步
public void run() {
while(true) {
try {
lock.lock();
if (ticket <= 0)
break;
try {
Thread.sleep(10); // 模拟一些耗时的操作
} catch(Exception e) {
e.printStackTrace();
}
System.out.println(getName() + " 卖出了第 " + ticket-- + " 号票");
} finally {
lock.unlock();
}
}
}
}